I only have basic knowledge about ASP.net. I have been writing a simple MVP controller, that processes a JSON post request. It worked fine until I tried injecting Hosting environment. Since then the server always responds with code 500, but there are no errors on the server itself. When setting breakpoints in the controller it seems like the post code is never reached, not even the constructor is called after the injection. I have no idea where to look for the reason for this. I hope someone can help me here. Here is the controller code:
using System;
using System.Net;
using System.Net.Http;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Authorization;
using Microsoft.AspNet.Mvc;
using PdfSharp;
using PdfSharp.Pdf;
using PdfSharp.Pdf.Annotations;
using PdfSharp.Pdf.Advanced;
using PdfSharp.Pdf.AcroForms;
using PdfSharp.Pdf.IO;
using st214.Models;
using Microsoft.AspNet.Hosting;
// For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860
namespace st214.API
{
[Route("api/[controller]")]
public class FormularController : Controller
{
private readonly ApplicationDbContext db;
private readonly HostingEnvironment _hostingEnvironment;
private readonly string tempLocation;
private Dictionary<string, string> locationsMock = new Dictionary<string, string>();
public FormularController(ApplicationDbContext context, HostingEnvironment hostingEnvironment)
{
db = context;
_hostingEnvironment = hostingEnvironment;
tempLocation = _hostingEnvironment.WebRootPath + #"\app\formularwesen\tempForms\";
locationsMock.Add(#"ct", #"CT_form.pdf");
locationsMock.Add(#"uewa", #"UeWA_form.pdf");
locationsMock.Add(#"uelab", #"UeWA_form.pdf");
locationsMock.Add(#"uelabalt", #"UeWA_form.pdf");
}
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public FileStreamResult Post([FromBody] FormData formData)
{
var source = _hostingEnvironment.WebRootPath + #"\app\formularwesen\PDFForms\" + locationsMock[formData.name];
var file = FillForm(source, tempLocation, formData.data, formData.print);
FileStream pdfStream = new FileStream(file, FileMode.Open, FileAccess.Read);
return new FileStreamResult(pdfStream, "application/pdf")
{
FileDownloadName = formData.name + ".pdf"
};
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody] FormData formData)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
private string FillForm(string source, string destinationFolder, Dictionary<string, FormField> fields, bool print)
{
// Open document
PdfDocument document = PdfReader.Open(source, PdfDocumentOpenMode.Modify);
// set fields to be editable
if (document.AcroForm.Elements.ContainsKey("/NeedAppearances") == false)
document.AcroForm.Elements.Add("/NeedAppearances", new PdfSharp.Pdf.PdfBoolean(true));
else
document.AcroForm.Elements["/NeedAppearances"] = new PdfSharp.Pdf.PdfBoolean(true);
// fill out fields
foreach (KeyValuePair<string, FormField> field in fields)
{
// get the field
if (field.Value.Type == "text")
{
PdfTextField currentField = (PdfTextField)(document.AcroForm.Fields[field.Key]);
// create the value
PdfString valueString = new PdfString(field.Value.Value);
// fill the value
try
{
currentField.Value = valueString;
}
catch (NullReferenceException e)
{
// Field not found
}
}
if (field.Value.Type == "radio")
{
PdfCheckBoxField currentField = (PdfCheckBoxField)(document.AcroForm.Fields[field.Value.Value]);
try
{
if (currentField.HasKids)
{
foreach (var item in currentField.Fields.Elements.Items)
{
//assumes you want to "check" the checkbox. Use "/Off" if you want to uncheck.
//"/Yes" is defined in your pdf document as the checked value. May vary depending on original pdf creator.
((PdfDictionary)(((PdfReference)(item)).Value)).Elements.SetName(PdfAcroField.Keys.V, "/Yes");
((PdfDictionary)(((PdfReference)(item)).Value)).Elements.SetName(PdfAnnotation.Keys.AS, "/Yes");
}
}
else
{
currentField.Checked = true;
}
}
catch (NullReferenceException e)
{
// field not found
}
}
}
// create unique file name (UUID)
string tmpName = Guid.NewGuid().ToString() + ".pdf";
string file = destinationFolder + tmpName;
// if file should be printed immediately, add print function to
// pdf document
if(print)
AddPrintFunction(document);
// save the document
document.Save(file);
return file;
}
public static void AddPrintFunction(PdfDocument document)
{
PdfDictionary dict = new PdfDictionary(document);
// According to the PDF Reference the dictionary requires two elements.
// A key /S that specifies the action name, and a key /JS that set the JavaScript to run.
dict.Elements["/S"] = new PdfName("/JavaScript");
dict.Elements["/JS"] = new PdfName("/this.print(true);");
document.Internals.AddObject(dict);
document.Internals.Catalog.Elements["/OpenAction"] = PdfInternals.GetReference(dict);
}
private string getTemplateFile(string templateName)
{
FormLocation template = db.FormLocations.Where(f => f.name == templateName).First();
return template.file;
}
}
}
Ok, I found it out myself. You need to inject IHostingEnvironment, not HostingEnvironment for it to work. I am not experienced enough to explain what each of those is, maybe someone can clarify it in the comments, just wanted to answer the question as I found the problem.
Related
so in the project a user could upload, download, delete, preview and search for (in real time) a file. The files are stored in a folder called "Uploads" inside of the wwwroot folder. As of now, the code has absolute pathing, but the project will need to be on different computers so I need relative pathing, can anyone help me out with a sample syntax?
[HttpPost]
public async Task<IActionResult> Index(IFormFile fifile, string category)
{
string path = #"C:\Users\panara5\source\repos\Proiect2PracticaClona\Proiect2PracticaClona\wwwroot\Uploads\";
var fisave = Path.Combine(path, fifile.FileName);
var stream = new FileStream(fisave, FileMode.Create);
await fifile.CopyToAsync(stream);
Files fileModel=new Files()
{
info = fifile.FileName,
category = category
};
_fileRepository.AddFile(fileModel);
stream.Close();
files = _fileRepository.GetFileList();
return RedirectToAction("Index",files);
}
public IActionResult Index(string id)
{
files = _fileRepository.GetFileList();
if (id == null)
return View(files);
List<Files> sortedfiles = new List<Files>();
foreach (var item in files)
{
if (item.category == id)
{
sortedfiles.Add(item);
}
}
return View(sortedfiles);
}
public IActionResult Delete(string filedel)
{
filedel = Path.Combine("~/Uploads/", filedel);
FileInfo fi = new FileInfo(filedel);
if (fi != null)
{
System.IO.File.Delete(filedel);
fi.Delete();
}
return RedirectToAction("Index");
}
also i get a null reference when trying to delete
I believe you want IWebHostEnvironment.WebRootPath
You need to use Dependency Injection to get access to it, but after that, you can just reference and Path.Combine. See below:
public class FileController : Controller
{
private readonly IWebHostEnvironment environment;
public FileController(IWebHostEnvironment env)
{
this.environment = env;
}
private string UploadFilePath() => Path.Combine(environment.WebRootPath, "uploads");
}
using this question I came up with this code to save a file
[HttpPost]
public IActionResult Upload(string office, IFormFile file)
{
if (file.Length > 0) {
var filePath = Path.GetFullPath(Path.Combine(_environment.WebRootPath,
_configuration["SydneyFloorplanPath"]));
using (var fileStream = new FileStream(filePath, FileMode.Create)) {
file.CopyTo(fileStream);
}
}
return RedirectToAction("Index", new{office = office});
}
But I get a super annoying issue when instantiating FileStream:
An exception of type 'System.IO.DirectoryNotFoundException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'Could not find a part of the path 'C:\images\Floorplan\sydney.pdf'.'
But That's not the path that I need. The Images folder sits in my project under wwwroot\images...
what is the point of WebRootPath if it doesn't actually give me a usable, relative path?
Note that _environment is an injected IHostingEnvironment
I also noticed that if I call
var two = _environment.WebRootPath;
the varaible contains the full path... "C:\\Users\\bassie\\source\\repos\\TFS\\DSSTools\\wwwroot"
But after calling Path.Combine, I suddenly have "C:\\images\\Floorplan\\sydney.pdf" ... why?
Edit:
_environment is a IHostingEnvironment, eg:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
namespace AspNetCorePathMapping
{
public class HomeController : Controller
{
private readonly IHostingEnvironment _hostingEnvironment;
public HomeController(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public ActionResult Index()
{
string webRootPath = _hostingEnvironment.WebRootPath;
string contentRootPath = _hostingEnvironment.ContentRootPath;
return Content(webRootPath + "\n" + contentRootPath);
}
}
}
I managed to get this working with
[HttpPost]
public IActionResult Upload(string office, IFormFile file)
{
var webRootPath = _environment.WebRootPath;
var floorPlanPath = _configuration["SydneyFloorplanPath"];
if (file.Length > 0) {
var filePath1 = Path.Combine(floorPlanPath,webRootPath.ReplaceFirst("/", ""));
using (var fileStream = new FileStream(filePath1, FileMode.Create)) {
file.CopyTo(fileStream);
}
}
return RedirectToAction("Index", new{office = office});
}
Where ReplaceFirst is a simple StringExtension
public static string ReplaceFirst(this string text, string search, string replace)
{
int pos = text.IndexOf(search);
if (pos < 0)
{
return text;
}
return $"{text.Substring(0, pos)}{replace}{text.Substring(pos + search.Length)}";
}
So it seems the leading / in webRootPath was breaking my Path.Combine call
Problem:
I need to render a Razor Page partial to a string.
Why I want this:
I want to create a controller action that responds with JSON containing a partial view and other optional parameters.
Attempts:
I am familiar with the following example that renders a View to a string: https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.RenderViewToString/RazorViewToStringRenderer.cs
However, it is not compatible with Pages, as it only searches in the Views directory, so even if I give it an absolute path to the partial it tries to locate my _Layout.cshtml (which it shouldn't even do!) and fails to find it.
I have tried to modify it so it renders pages, but I end up getting a NullReferenceException for ViewData in my partial when attempting to render it. I suspect it has to do with NullView, but I have no idea what to put there instead (the constructor for RazorView requires many objects that I don't know how to get correctly).
The code:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0: https://www.apache.org/licenses/LICENSE-2.0
// Modified by OronDF343: Uses pages instead of views.
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing;
namespace TestAspNetCore.Services
{
public class RazorPageToStringRenderer
{
private readonly IRazorViewEngine _viewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public RazorPageToStringRenderer(
IRazorViewEngine viewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public async Task<string> RenderPageToStringAsync<TModel>(string viewName, TModel model)
{
var actionContext = GetActionContext();
var page = FindPage(actionContext, viewName);
using (var output = new StringWriter())
{
var viewContext = new ViewContext(actionContext,
new NullView(),
new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(actionContext.HttpContext,
_tempDataProvider),
output,
new HtmlHelperOptions());
page.ViewContext = viewContext;
await page.ExecuteAsync();
return output.ToString();
}
}
private IRazorPage FindPage(ActionContext actionContext, string pageName)
{
var getPageResult = _viewEngine.GetPage(executingFilePath: null, pagePath: pageName);
if (getPageResult.Page != null)
{
return getPageResult.Page;
}
var findPageResult = _viewEngine.FindPage(actionContext, pageName);
if (findPageResult.Page != null)
{
return findPageResult.Page;
}
var searchedLocations = getPageResult.SearchedLocations.Concat(findPageResult.SearchedLocations);
var errorMessage = string.Join(
Environment.NewLine,
new[] { $"Unable to find page '{pageName}'. The following locations were searched:" }.Concat(searchedLocations));
throw new InvalidOperationException(errorMessage);
}
private ActionContext GetActionContext()
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
}
}
}
This is how I did it.
As always register the Service in Startup.cs
services.AddScoped<IViewRenderService, ViewRenderService>();
The Service is defined as follows:
public interface IViewRenderService
{
Task<string> RenderToStringAsync<T>(string viewName, T model) where T : PageModel;
}
public class ViewRenderService : IViewRenderService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
private readonly IHttpContextAccessor _httpContext;
private readonly IActionContextAccessor _actionContext;
private readonly IRazorPageActivator _activator;
public ViewRenderService(IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider,
IHttpContextAccessor httpContext,
IRazorPageActivator activator,
IActionContextAccessor actionContext)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
_httpContext = httpContext;
_actionContext = actionContext;
_activator = activator;
}
public async Task<string> RenderToStringAsync<T>(string pageName, T model) where T : PageModel
{
var actionContext =
new ActionContext(
_httpContext.HttpContext,
_httpContext.HttpContext.GetRouteData(),
_actionContext.ActionContext.ActionDescriptor
);
using (var sw = new StringWriter())
{
var result = _razorViewEngine.FindPage(actionContext, pageName);
if (result.Page == null)
{
throw new ArgumentNullException($"The page {pageName} cannot be found.");
}
var view = new RazorView(_razorViewEngine,
_activator,
new List<IRazorPage>(),
result.Page,
HtmlEncoder.Default,
new DiagnosticListener("ViewRenderService"));
var viewContext = new ViewContext(
actionContext,
view,
new ViewDataDictionary<T>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(
_httpContext.HttpContext,
_tempDataProvider
),
sw,
new HtmlHelperOptions()
);
var page = ((Page)result.Page);
page.PageContext = new Microsoft.AspNetCore.Mvc.RazorPages.PageContext
{
ViewData = viewContext.ViewData
};
page.ViewContext = viewContext;
_activator.Activate(page, viewContext);
await page.ExecuteAsync();
return sw.ToString();
}
}
}
I call it like this
emailView.Body = await this._viewRenderService.RenderToStringAsync("Email/ConfirmAccount", new Email.ConfirmAccountModel
{
EmailView = emailView,
});
"Email/ConfirmAccount" is the path to my Razor page (Under pages). "ConfirmAccountModel" is my page model for that page.
ViewData is null because the ViewData for the Page is set when the PageContext is set, so if this is not set ViewData is null.
I also found that I had to call
_activator.Activate(page, viewContext);
For it all to work. This is not fully tested yet so may not work for all scenarios but should help you get started.
If like me you don't get GetRouteData() from _httpContext.HttpContext and _actionContext is null, you can create an extension:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace Utils
{
public static class PageExtensions
{
public static async Task<string> RenderViewAsync(this PageModel pageModel, string pageName)
{
var actionContext = new ActionContext(
pageModel.HttpContext,
pageModel.RouteData,
pageModel.PageContext.ActionDescriptor
);
using (var sw = new StringWriter())
{
IRazorViewEngine _razorViewEngine = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorViewEngine)) as IRazorViewEngine;
IRazorPageActivator _activator = pageModel.HttpContext.RequestServices.GetService(typeof(IRazorPageActivator)) as IRazorPageActivator;
var result = _razorViewEngine.FindPage(actionContext, pageName);
if (result.Page == null)
{
throw new ArgumentNullException($"The page {pageName} cannot be found.");
}
var page = result.Page;
var view = new RazorView(_razorViewEngine,
_activator,
new List<IRazorPage>(),
page,
HtmlEncoder.Default,
new DiagnosticListener("ViewRenderService"));
var viewContext = new ViewContext(
actionContext,
view,
pageModel.ViewData,
pageModel.TempData,
sw,
new HtmlHelperOptions()
);
var pageNormal = ((Page)result.Page);
pageNormal.PageContext = pageModel.PageContext;
pageNormal.ViewContext = viewContext;
_activator.Activate(pageNormal, viewContext);
await page.ExecuteAsync();
return sw.ToString();
}
}
}
}
Note: this code only render the page being called and omit the layout.
You just have to call it from your PageModel like this:
var s = this.RenderViewAsync("sendEmail").Result;
"sendEmail" is the name of your PageModel view and the path is /Pages/sendEmail.cshtml
Here is the route I have gone down. Very simple and works a treat...
using System;
using System.IO;
using System.Net;
namespace gMIS.Rendering
{
public static class RazorPage
{
public static string RenderToString(string url)
{
try
{
//Grab page
WebRequest request = WebRequest.Create(url);
WebResponse response = request.GetResponse();
Stream data = response.GetResponseStream();
string html = String.Empty;
using (StreamReader sr = new StreamReader(data))
{
html = sr.ReadToEnd();
}
return html;
}
catch (Exception err)
{
return {Handle as you see fit};
}
}
}
}
Called as such....
var msg = RazorPage.RenderToString(url);
Example:
var pathToRazorPageFolder = request.PathToRazorPageFolder();
var msg = RazorPage.RenderToString($"{pathToRazorPageFolder}/Task_Summary?userGuid={userGuid}&taskId={task.Task_ID}&includelink=true&linkuserGuid={linkUserGuid}");
Above example uses this extension I created to help get the base path of my app.
namespace Microsoft.AspNetCore.Http
{
public static class RequestExtension
{
public static string PathToRazorPageFolder(this HttpRequest request)
{
if (request != null) {
var requestPath = request.Path.ToString();
var returnPathToFolder = request.Scheme + "://" + request.Host + requestPath.Substring(0, requestPath.LastIndexOf("/")); ;
return returnPathToFolder;
} else
{
return "HttpRequest was null";
}
}
}
}
I know that this doesn't use Dependency Injection, but man it's simple. And it just works. And works with any page no matter how it is hosted. Be that page be within or even outside your application.
I had the same problem.
I looked into the RazorViewEngine source code and found out that the page
is searched using the "page" route data:
var routeData = new RouteData();
routeData.Values.Add("page", "/Folder/MyPage");
It's working for me with the full path "/Folder/MyPage" in the routeData, and the page name "MyPage" in the GetPage call.
Sample of my request
http://localhost:8065/api/note
POST
content-type:application/json
request body: { "id" : "1234", "title" : "test", "status" : "draft"}
and the response should be
{ "msg" : "ok", "code" : 1}
The action
public async Task<IActionResult> Post([FromBody]NoteModel model)
In order to have every request logged automatically, I create an attribute to do this job. The attribute looks like: (from Microsoft Docs)
public class SampleActionFilterAttribute : TypeFilterAttribute
{
public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
{
}
private class SampleActionFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public SampleActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
_logger.LogDebug("[path]" + context.HttpContext.Request.Path);
_logger.LogDebug("[method]" + context.HttpContext.Request.Method);
_logger.LogDebug("[body]"); //log request body, expectation: { "id" : "1234", "title" : "test", "status" : "draft"}
_logger.LogDebug("[statuscode]" + context.HttpContext.Response.StatusCode);
_logger.LogDebug("[response]"); //log response
}
}
}
I try to use streamReader to get request body only get empty string.
StreamReader reader = new StreamReader(context.HttpContext.Request.Body);
string text = reader.ReadToEnd();
Is that because the body was read by [fromBody] from controller so the stream can not be read twice? If so, how am I supposed to get request body and response in OnActionExecuted method?
Update:
I've just copied Set's code into my project, not working. Here is the debug gif
Accordingly to this "Best way to log/read request body in a middleware" thread, the following should work:
// using Microsoft.AspNetCore.Http.Internal;
public class SampleActionFilterAttribute : TypeFilterAttribute
{
...
public void OnActionExecuting(ActionExecutedContext context)
{
// read body before MVC action execution
string bodyData = ReadBodyAsString(context.HttpContext.Request);
}
private string ReadBodyAsString(HttpRequest request)
{
var initialBody = request.Body; // Workaround
try
{
request.EnableRewind();
using (StreamReader reader = new StreamReader(request.Body))
{
string text = reader.ReadToEnd();
return text;
}
}
finally
{
// Workaround so MVC action will be able to read body as well
request.Body = initialBody;
}
return string.Empty;
}
}
Also similar approach described in Read request body twice SO post
Update: above approach in ReadBodyAsString with will work if used in middleware, not in action filter. The difference is that when action filter is calling (even for OnActionExecuting), the body stream already has been read and [FromBody] model has been populated.
The good nesw is that so it is possible to get model directly in action filter by using context.ActionArguments["<model_name>"]. In your case:
public void OnActionExecuted(ActionExecutedContext context)
{
var model = context.ActionArguments["model"] as NoteModel;
}
Following snippet worked for me, to log request only if there is any exception.(.Net Core 3.1)
{
public class ExceptionFilter : IActionFilter
{
private ConcurrentDictionary<string, string> requests = new ConcurrentDictionary<string, string>();
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
StringBuilder parameters = new StringBuilder();
_logger.LogError("Error while executing action:" + context.ActionDescriptor.DisplayName);
string errRequest;
if(requests.TryGetValue(context.HttpContext.TraceIdentifier,out errRequest))
{
_logger.LogError(errRequest);
}
_logger.LogError(context.Exception);
context.Result = new ObjectResult("Error!!!")
{
StatusCode = 500,
};
context.ExceptionHandled = true;
}
string req;
requests.Remove(context.HttpContext.TraceIdentifier, out req);
}
public void OnActionExecuting(ActionExecutingContext context)
{
StringBuilder sb = new StringBuilder();
foreach (var arg in context.ActionArguments)
{
sb.Append(arg.Key.ToString() + ":" + Newtonsoft.Json.JsonConvert.SerializeObject(arg.Value) + "\n");
}
requests.TryAdd(context.HttpContext.TraceIdentifier, sb.ToString());
}
}
}
in .net core 3.1, I usually use this approach to handle this scenario
1- Create Async Filter Attribute
public class MyFilter : IAsyncAuthorizationFilter
{
private string _errorMessage = UserAccessErrorMessages.NO_ACCESS;
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
string requestBody = await ReadBodyAsString(context.HttpContext.Request);
var reportFiltration = JsonConvert.DeserializeObject<YourModel>(requestBody);
var _myService = (IMyService)context.HttpContext.RequestServices.GetService(typeof(IMyService));
if (!hasAccess)
{
context.Result = new UnauthorizedObjectResult(_errorMessage);
}
}
private async Task<string> ReadBodyAsString(Microsoft.AspNetCore.Http.HttpRequest request)
{
var initialBody = request.Body; // Workaround
try
{
//request.EnableRewind();
using (StreamReader reader = new StreamReader(request.Body))
{
string text = await reader.ReadToEndAsync();
return text;
}
}
finally
{
// Workaround so MVC action will be able to read body as well
request.Body = initialBody;
}
}
}
2- Create Your Custom Attribute
public class MyAttribute : TypeFilterAttribute, IAllowAnonymous
{
public MyAttribute () : base(typeof(MyFilter))
{
}
}
3- use your filter
[HttpPost]
[ActionName("MyAction")]
[MyAttribute]
public async Task<IActionResult> PostData([FromBody]MyModel model)
If you're using IActionResult in your controllers and you want the .NET objects, you can write a filter like this:
public class SampleFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult)
{
var objResult = (ObjectResult)context.Result;
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
}
}
By the point it hits OnActionExecuted, the ObjectResult task has already completed, so you can just extract the value. You can also get the StatusCode with objResult.StatusCode.
In the controller, return Ok(...) actually creates an OkObjectResult, etc.
If you specifically want the serialzied result, then Set's answer is more valid.
I'm currently writing a simple controller method in my MVC WEB Api application which downloads file from a Dropbox account. The problem is I can't return the file from the method: it says "The name 'File' does not exist in the current context", but in the documentation this constructor can be called.
Code:
public async Task<FileResult> GetFile(string folder, string file)
{
using (var dbx = new DropboxClient("generated token key"))
{
using (var response = await dbx.Files.DownloadAsync(folder + "/" + file))
{
var result = await response.GetContentAsStringAsync();
return File(result, file);
}
}
}
Complete DropBoxController.cs class:
using Dropbox.Api;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Mvc;
namespace TestDropBox.Controllers
{
public class DropBoxController : ApiController
{
// GET: api/DropBox
public async Task<IEnumerable<string>> Get()
{
List<string> AccountData= new List<string>();
using (var dbx = new DropboxClient("generated token"))
{
var full = await dbx.Users.GetCurrentAccountAsync();
Console.WriteLine("{0} - {1}", full.Name.DisplayName, full.Email);
AccountData.Add(full.Name.DisplayName);
AccountData.Add(full.Email);
}
return DatiAccount;
}
[System.Web.Http.HttpGet]
public async Task<FileResult> GetFile(string folder, string file)
{
using (var dbx = new DropboxClient("generated token"))
{
using (var response = await dbx.Files.DownloadAsync(folder + "/" + file))
{
var result = await response.GetContentAsStringAsync();
return new File(result, file);
}
}
}
// GET: api/DropBox/5
public string Get(int id)
{
return "value";
}
// POST: api/DropBox
public void Post([FromBody]string value)
{
}
// PUT: api/DropBox/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE: api/DropBox/5
public void Delete(int id)
{
}
}
}
How could I fix this problem? Thank you.
You inherit from ApiController, so the System.Web.Mvc.File ctor isn't accessible (is protected), that's why you recive the error.
Have a look at this thread:
File Download with api controller
You are using ApiController. Your updated method could look something like...
[System.Web.Http.HttpGet]
public async Task<HttpResponseMessage> GetFile(string folder, string fileName) {
using (var dbx = new DropboxClient("generated token")) {
using (var file = await dbx.Files.DownloadAsync(folder + "/" + fileName)) {
var content = await file.GetContentAsStringAsync();
var statuscode = HttpStatusCode.OK;
var response = Request.CreateResponse(statuscode);
var stream = new MemoryStream(Encoding.UTF8.GetBytes(content ?? ""));
response.Content = new StreamContent(stream);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
FileName = fileName
};
return response;
}
}
}