I have built a MVC Controller that will be used throughout the site to download files for an API. I am not able to call it like normal with the Mvc\controllers\ControllerName\method. I get a 404 error page not found. I did do so testing with adding ControllerToolboxItem and adding it to a page. Once I add it to the page I can call the method. Using the PageName\method?filePath="" so I know the method is working. Is there something I am missing in my Controller that I need to add for it to be called throughout the site?
public class CommonController : Controller
{
[HttpGet]
public HttpResponseMessage GetDataFileResponse(string filePath) {
try {
FileStream fileStream = File.OpenRead(filePath);
long fileLength = new FileInfo(filePath).Length;
var response = new HttpResponseMessage();
response.Content = new StreamContent(fileStream);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "mydata.csv";
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
response.Content.Headers.ContentLength = fileLength;
return response;
} catch (Exception e) {
Console.WriteLine(e);
throw;
}
}
}
You can add the widget to a base page template, so now it will be available on all pages.
Or see if you can make it an web api controller and then there will be no widget, but you will be able to access it via /yourapiroute/common/getdataFileResponse?filepath=xxx
I've written an article in the past, which might be of help: https://sitefinitydevelopment.com/blog/uploading-files-to-sitefinity-asynchronously-using-kendo-ui-upload.html
Related
I am trying to build a service client to simplify calling my microservices in .net core.
Here is a service client sample:
public ProductServiceClient(SystemEnvironment.MachineEnvironment? environment = null)
{
this.url = ServiceEnvironment.Urls.GetUrl(ServiceEnvironment.Service.Product, environment);
}
private RestClient GetClient(string method)
{
return new RestClient(url + "/api/" + method);
}
private RestRequest GetRestRequest(Method method)
{
var restRequest = new RestRequest(method);
restRequest.RequestFormat = DataFormat.Json;
restRequest.AddHeader("Content-Type", "application/json");
return restRequest;
}
public FindProductsResponse FindProducts(FindProductsRequest request)
{
var restRequest = GetRestRequest(Method.GET);
restRequest.AddJsonBody(request);
var client = this.GetClient("Products");
var restResponse = client.Get(restRequest);
return new JsonDeserializer().Deserialize<FindProductsResponse>(restResponse);
}
public void Dispose()
{
}
And here is how I am trying to read it in my .net core api:
[HttpGet]
public ActionResult<FindProductsResponse> Get()
{
var request = "";
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
request = reader.ReadToEnd();
}
var buildRequest = JsonConvert.DeserializeObject<FindProductsRequest>(request);
var products = _service.FindProducts(buildRequest);
if (products != null && products.Any())
{
return new FindProductsResponse()
{
Products = products
};
}
return BadRequest("Not found");
}
However the request variable is always empty after Request.Body has been processed by the StreamReader.
If I make the same request from Postman (also using GET), I get the body just fine.
What am I doing wrong here?
EDIT: This is the unit test calling the api:
[Test]
public void Test1()
{
using (var productServiceClient = new ProductServiceClient())
{
var products = productServiceClient.FindProducts(new FindProductsRequest()
{
Id = 50
}).Products;
}
}
It can be your Request.Body has been already consumed.
Try to call Request.EnableRewind() before to open the StreamReader.
I'm not sure why you are manually doing it. It looks like you are reinventing the wheel. ASP.NET Core already does that for you.
This is what your service should look like:
[HttpGet] // oops, GET requests will not allow Bodies, this won't work
public ActionResult<FindProductsResponse> Get([FromBody]FindProductsRequest buildRequest)
{
// skip all the serialization stuff, the framework does that for you
var products = _service.FindProducts(buildRequest);
if (products != null && products.Any())
{
return new FindProductsResponse()
{
Products = products
};
}
return BadRequest("Not found");
}
And if you don't want to redo all the busy work that is retyping all the code on the client side, I suggest you read up on swagger (probably in the form of Swashbuckle). Client code can be generated. Even from within Visual Studio, if you right-click on the project and in the context menu pick "Add REST API Client...". Please don't erroneously hand-code what can be generated flawlessly by a machine instead. I don't really know what went wrong in your specific case, but searching bugs that could be avoided altogether is just busywork, that time should be spent on other parts of the program.
I just realized this is a GET request. ASP.NET will not recognize bodies for GET-Requests. You will need to make it a PUT or POST request or put your parameters in the query string.
If you happen to make that mistake as often as I did, you might want to write some unit tests that cover this. Because .NET is not helping you there. Been there, done that..
I need a full working example for implementing the remove functionality in telerik kendo angular with Angular 8 and a backend web API in ASP.net core 2.2
In kendo Angular I'm trying to implement the functionality of upload files and removing it when user clicks the x small buttons
I've already implemented the upload so perfectly but when providing the removeUrl it gives error 404 or 415
The other strange thing is when I provide the same url for the upload and download it binds and code could go well but i need a separate action for the remove functionality
[HttpPost("upload"), DisableRequestSizeLimit]
public async Task<IActionResult> Post(IFormFileCollection files)
{
long size = files.Sum(f => f.Length);
//var reqFiles = Request.Form.Files;
var uploads = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
if (!Directory.Exists(uploads))
{
Directory.CreateDirectory(uploads);
}
List<string> paths = new List<string>();
foreach (var file in files)
{
if (file.Length > 0)
{
string fileName = Guid.NewGuid() + Path.GetExtension(file.FileName);
var filePath = Path.Combine(uploads, fileName);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(fileStream);
}
paths.Add(filePath);
}
}
// process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size, paths });
}
and for delete
[HttpPost]
private IActionResult Delete(IList<string> files)
{
// var reqFiles = Request.Form.Files;
var uploads = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
foreach (var item in files)
{
var filePath = Path.Combine(uploads, item);
System.IO.File.Delete(filePath);
}
return Ok(new { files });
}
Error 404 is raised when trying to name the action in web api and provide this name in the url (although the same thing is working when uploadling) and 415 is being raised when using post request without naming the action in the back end
I'm trying to access a request's raw input body/stream in ASP.net 5. In the past, I was able to reset the position of the input stream to 0 and read it into a memory stream but when I attempt to do this from the context the input stream is either null or throws an error (System.NotSupportedException => "Specified method is not supported.").
In the first example below I can access the raw request in a controller if I declare the controller method's parameter object type as dynamic. For various reasons, this is not a solution and I need to access the raw request body in an authentication filter anyways.
This Example Works, But Is Not a Reasonable Solution:
[HttpPost("requestme")]
public string GetRequestBody([FromBody] dynamic body)
{
return body.ToString();
}
Throws Error:
[HttpPost("requestme")]
public string GetRequestBody()
{
var m = new MemoryStream();
Request.Body.CopyTo(m);
var contentLength = m.Length;
var b = System.Text.Encoding.UTF8.GetString(m.ToArray());
return b;
}
Throws Error:
[HttpPost("requestme")]
public string GetRequestBody()
{
Request.Body.Position = 0;
var input = new StreamReader(Request.Body).ReadToEnd();
return input;
}
Throws Error:
[HttpPost("requestme")]
public string GetRequestBody()
{
Request.Body.Position = 0;
var input = new MemoryStream();
Request.Body.CopyTo(input);
var inputString = System.Text.Encoding.UTF8.GetString(input.ToArray());
return inputString;
}
I need to access the raw request body of every request that comes in for an API that I am building.
Any help or direction would be greatly appreciated!
EDIT:
Here is the code that I would like to read the request body in.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Http;
namespace API.Filters
{
public class CustomAuthorizationAttribute : Attribute, IAuthorizationFilter
{
public CustomAuthorizationAttribute()
{ }
public void OnAuthorization(AuthorizationContext context)
{
if (context == null)
throw new ArgumentNullException("OnAuthorization AuthorizationContext context can not be null.");
else
{
if (this.AuthorizeCore(context.HttpContext) == false)
{
// Do Other Stuff To Check Auth
}
else
{
context.Result = new HttpUnauthorizedResult();
}
}
}
protected virtual bool AuthorizeCore(HttpContext httpContext)
{
var result = false;
using (System.IO.MemoryStream m = new System.IO.MemoryStream())
{
try
{
if (httpContext.Request.Body.CanSeek == true)
httpContext.Request.Body.Position = 0;
httpContext.Request.Body.CopyTo(m);
var bodyString = System.Text.Encoding.UTF8.GetString(m.ToArray());
return CheckBody(bodyString); // Initial Auth Check returns true/false <-- Not Shown In Code Here on Stack Overflow
}
catch (Exception ex)
{
Logger.WriteLine(ex.Message);
}
}
return false;
}
}
}
This code would be accessed when a call is made to a controller method marked with the CustomAuthorization attribute like so.
[Filters.CustomAuthorizationAuthorization]
[HttpPost]
public ActionResult Post([FromBody]UserModel Profile)
{
// Process Profile
}
Update
The information below is pretty outdated by now. Due to performance reasons this is not possible by default, but fortunately can be changed. The latest solution should be to enable request buffering with EnableBuffering:
Request.EnableBuffering();
See also this blog post for more information: https://devblogs.microsoft.com/aspnet/re-reading-asp-net-core-request-bodies-with-enablebuffering/.
Old, outdated answer for reference
The implementation of Request.Body depends on the controller action.
If the action contains parameters it's implemented by Microsoft.AspNet.WebUtilities.FileBufferingReadStream, which supports seeking (Request.Body.CanSeek == true). This type also supports setting the Request.Body.Position.
However, if your action contains no parameters it's implemented by Microsoft.AspNet.Loader.IIS.FeatureModel.RequestBody, which does not support seeking (Request.Body.CanSeek == false). This means you can not adjust the Position property and you can just start reading the stream.
This difference probably has to do with the fact that MVC needs to extract the parameters values from the request body, therefore it needs to read the request.
In your case, your action does not have any parameters. So the Microsoft.AspNet.Loader.IIS.FeatureModel.RequestBody is used, which throws an exception if you try to set the Position property.
**Solution**: either do not set the position or check if you actually _can_ set the position first:
if (Request.Body.CanSeek)
{
// Reset the position to zero to read from the beginning.
Request.Body.Position = 0;
}
var input = new StreamReader(Request.Body).ReadToEnd();
The exceptions you see in your three last snippets are the direct consequence of trying to read the request body multiple times - once by MVC 6 and once in your custom code - when using a streamed host like IIS or WebListener. You can see this SO question for more information: Read body twice in Asp.Net 5.
That said, I'd only expect this to happen when using application/x-www-form-urlencoded, since it wouldn't be safe for MVC to start reading the request stream with lengthy requests like file uploads. If that's not the case, then it's probably a MVC bug you should report on https://github.com/aspnet/Mvc.
For workarounds, you should take a look at this SO answer, that explains how you can use context.Request.ReadFormAsync or add manual buffering: Read body twice in Asp.Net 5
app.Use(next => async context => {
// Keep the original stream in a separate
// variable to restore it later if necessary.
var stream = context.Request.Body;
// Optimization: don't buffer the request if
// there was no stream or if it is rewindable.
if (stream == Stream.Null || stream.CanSeek) {
await next(context);
return;
}
try {
using (var buffer = new MemoryStream()) {
// Copy the request stream to the memory stream.
await stream.CopyToAsync(buffer);
// Rewind the memory stream.
buffer.Position = 0L;
// Replace the request stream by the memory stream.
context.Request.Body = buffer;
// Invoke the rest of the pipeline.
await next(context);
}
}
finally {
// Restore the original stream.
context.Request.Body = stream;
}
});
I just had this same issue. Remove the parameters from the method signature, and then read the Request.Body Stream how you want to.
You need to call Request.EnableRewind() to allow the stream to be rewound so you can read it.
string bodyAsString;
Request.EnableRewind();
using (var streamReader = new StreamReader(Request.Body, Encoding.UTF8))
{
bodyAsString = streamReader.ReadToEnd();
}
I Know this my be late but in my case its Just I had a problem in routing as bellow
At startup.cs file I was beginning the routing with /api
app.MapWhen(context => context.Request.Path.StartsWithSegments(new PathString("/api")),
a =>
{
//if (environment.IsDevelopment())
//{
// a.UseDeveloperExceptionPage();
//}
a.Use(async (context, next) =>
{
// API Call
context.Request.EnableBuffering();
await next();
});
}
//and I was putting in controller
[HttpPost]
[Route("/Register", Name = "Register")]
//Just Changed the route to start with /api like my startup.cs file
[HttpPost]
[Route("/api/Register", Name = "Register")]
//and now the params are not null and I can ready the body request multiple
I'm currently updating an old site to use the Hot Towel framework and one of the the things the current site does is load an image using the HTTPMessageResponse. This works fine in the the old set up however on the new set up the image is not displayed and going to the URL produces...
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.ByteArrayContent, Headers: { Content-Type: image/png }
I've had a look at the response both apps produce, and the responses from the following function match (the response is the same as above and the contents are a perfect match)...
namespace OPC.Controllers
{
public class ImageController : Controller
{
public HttpResponseMessage GetImage(int imageId)
{
var blob = new byte[10];
var context = new OPCEntity();
var ImageString = context.sp_GetImageData(imageId);
foreach (var Result in ImageString)
{
blob = Result.Data;
}
Image image;
var ms = new MemoryStream();
ms.Write(blob, 0, blob.Length);
var result = new HttpResponseMessage(HttpStatusCode.OK);
result.Content = new ByteArrayContent(ms.ToArray());
result.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");
return result;
}
}
}
The image is set in the JS using...
image.setAttribute('src', document.location.href.match(/(^[^#]*)/)[0] + '/Image/GetImage?imageId=' + imageDetails.imageId);
Does anyone have any ideas on what I could be missing, or if anything in the Hot Towel framework could be interfering.
Many Thanks in advance.
Well I have missed something important... the fact that it should be...
public class ImageController : ApiController{}
However I have also found that the breeze web API config does conflict with the normal web API config so for the time being I've had to change BreezeWebApiConfig.cs to the following
public static class BreezeWebApiConfig {
public static void RegisterBreezePreStart() {
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "api/{controller}/{action}"
);
}
}
When calling the method shown below, Request is always null. I have some simple methods returning JSON data from controllers in an MVC4 app with controllers using ApiController as a base class. The code for my directory function is as follows:
public HttpResponseMessage GetDirectory() {
try {
var dir = r.GetDirectory();
if (dir == null) {
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError));
}
var response = Request.CreateResponse(HttpStatusCode.OK, dir, "application/json");
response.Headers.Location = new Uri(Request.RequestUri, "directory");
return response;
} catch (Exception ex) {
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
}
}
When this method is called, 'dir' is loaded properly from r.GetDirectory(). But Request is null. So naturally Request.CreateResponse() fails, and so on. I am looking for reasons why Request would be null, or for a rewrite that allows the return to remain an HttpResponseMessage.
This is being called (in my unit test project) with:
var ctrl = new DirectoryController();
var httpDir = ctrl.GetDirectory();
Thanks for your help.
Olav Nybø left me the hint that led to the answer. In response to ASP.NET WebApi unit testing with Request.CreateResponse, jonnii suggested the following code:
controller.Request = new HttpRequestMessage();
controller.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
This led to a nice way to test controllers:
var controller = new RecordsController();
controller.Request = new HttpRequestMessage();
controller.Request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, new HttpConfiguration());
var json = controller.GetAllRecords(id);
var records = JsonConvert.DeserializeObject<DynamicRecordSet>(json.Content.ReadAsStringAsync().Result);