FilePathResult or custom Action Result? - asp.net-mvc

To send pdf files (or any other file type) to the browser, I've seen examples that use the FileResult class or a custom Action Result.
Is there an advantage of using one over the other? Thanks

EDIT Original reply removed.
I'm not sure if the exist file result allows to you modify the content disposition, I believe it forces the content disposition of "attachment". If you want to use a different disposition, I'd implement a custom Action Filter:
/// <summary>
/// Defines an <see cref="ActionResult" /> that allows the output of an inline file.
/// </summary>
public class InlineFileResult : FileContentResult
{
#region Constructors
/// <summary>
/// Writes the binary content as an inline file.
/// </summary>
/// <param name="data">The data to be written to the output stream.</param>
/// <param name="contentType">The content type of the data.</param>
/// <param name="fileName">The filename of the inline file.</param>
public InlineFileResult(byte[] data, string contentType, string fileName)
: base(data, contentType)
{
FileDownloadName = fileName;
}
#endregion
#region Methods
/// <summary>
/// Executes the result, by writing the contents of the file to the output stream.
/// </summary>
/// <param name="context">The context of the controller.</param>
public override void ExecuteResult(ControllerContext context)
{
if (context == null) {
throw new ArgumentNullException("context");
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = this.ContentType;
if (!string.IsNullOrEmpty(this.FileDownloadName)) {
ContentDisposition disposition = new ContentDisposition();
disposition.FileName = FileDownloadName;
disposition.Inline = true;
context.HttpContext.Response.AddHeader("Content-Disposition", disposition.ToString());
}
WriteFile(response);
}
#endregion
}
That is one I've used previously, because I pass the actual byte[] data of the file.

They would essentially do the same thing. Setting up a FileContentResult is probably the most straight-forward and easiest to get started with:
public FileContentResult GetPdf()
{
return File(/* byte array contents */, "application/pdf");
}
If you wanted to save yourself from having to specify the content type (if you are always going to do PDF), you could create an ActionResult that specified the content type (application/pdf) inside of it, and return that instead of the FileContentResult (maybe something like PdfContentResult).
But like I said, they will do the same thing and there will not be any performance difference.

Related

PlannerTaskDetail's GetETag returns null. If-Match header not working

I'm trying to update task details:
var t = await graphClient.Planner.Tasks[task.Id].Details.Request().GetAsync();
var ETag = t.GetETag();
However GetETag() returns null.
The ETag is part of the AdditionalData dictionary, so I can extract it writing my own method.
For example:
var e = etag = (taskDetails.AdditionalData["#odata.etag"] as JsonElement?)?.GetString();
But why does the built-in method not work? My code is almost exactly the same as Microsoft's test code.
Thanks.
Please debug and check GetEtag() method that is exposed here. Also validate the entity passed onto this method is not null.
namespace Microsoft.Graph
{
using System;
/// <summary>
/// Helper class to extract #odata.etag property and to specify If-Match headers for requests.
/// </summary>
public static class EtagHelper
{
/// <summary>
/// Name of the OData etag property.
/// </summary>
public const string ODataEtagPropertyName = "#odata.etag";
/// <summary>
/// Returns the etag of an entity.
/// </summary>
/// <param name="entity">The entity that contains an etag.</param>
/// <returns>Etag value if present, null otherwise.</returns>
public static string GetEtag(this Entity entity)
{
if (entity == null)
{
throw new ArgumentNullException(nameof(entity));
}
entity.AdditionalData.TryGetValue(ODataEtagPropertyName, out object etag);
return etag as string;
}
}
}

Swashbuckle ASP.NET Core consume application/x-www-form-urlencoded

I have an Action that consumes application/x-www-form-urlencoded:
[HttpPost("~/connect/token"), Consumes("application/x-www-form-urlencoded")]
public async Task<IActionResult> Exchange([FromBody]OpenIdConnectRequest request)
{
..
}
But Swashbuckle generates empty array for Consumes property. If I change it to application/json, consumes array is generated properly.
Is it a bug related to application/x-www-form-urlencoded or I need to configure Swashbuckle additionally to support this application type?
That 'consumes' is not catered for out-of-the-box for Swashbuckle, a custom extension is required like the ones in this part of #domaindrivendev's GitHub project
There are three steps:
Create Parameter Attribute
Create Extension
Add instruction to Swashbuckle to process the new extension
Add an attribute to the parameter in an Controller method
I'll add more instructions in my fork of the repo, but here is the code:
1. FromFormDataBodyAttribute.cs
using System;
using System.Collections.Generic;
using System.Net.Http.Formatting;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Validation;
/// <summary>
/// FromFormDataBody Attribute
/// This attribute is used on action parameters to indicate
/// they come only from the content body of the incoming HttpRequestMessage.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
public sealed class FromFormDataBodyAttribute : ParameterBindingAttribute
{
/// <summary>
/// GetBinding
/// </summary>
/// <param name="parameter">HttpParameterDescriptor</param>
/// <returns>HttpParameterBinding</returns>
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
if (parameter == null)
throw new ArgumentNullException("parameter");
IEnumerable<MediaTypeFormatter> formatters = parameter.Configuration.Formatters;
IBodyModelValidator validator = parameter.Configuration.Services.GetBodyModelValidator();
return parameter.BindWithFormatter(formatters, validator);
}
}
2 AddUrlFormDataParams.cs
using Swashbuckle.Swagger;
using System.Linq;
using System.Web.Http.Description;
/// <summary>
/// Add UrlEncoded form data support for Controller Actions that have FromFormDataBody attribute in a parameter
/// usage: c.OperationFilter<AddUrlFormDataParams>();
/// </summary>
public class AddUrlFormDataParams : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
var fromBodyAttributes = apiDescription.ActionDescriptor.GetParameters()
.Where(param => param.GetCustomAttributes<FromFormDataBodyAttribute>().Any())
.ToArray();
if (fromBodyAttributes.Any())
operation.consumes.Add("application/x-www-form-urlencoded");
foreach (var headerParam in fromBodyAttributes)
{
if (operation.parameters != null)
{
// Select the capitalized parameter names
var parameter = operation.parameters.Where(p => p.name == headerParam.ParameterName).FirstOrDefault();
if (parameter != null)
{
parameter.#in = "formData";//NB. ONLY for this 'complex' object example, as it will be passed as body JSON.
//TODO add logic to change to "query" for string/int etc. as they are passed via query string.
}
}
}
}
}
3 Update Swagger.config
//Add UrlEncoded form data support for Controller Actions that have FromBody attribute in a parameter
c.OperationFilter<AddUrlFormDataParams>();
4. Add an attribute to the parameter in an Controller method
[FromFormDataBody]OpenIdConnectRequest request

Unity: Implicit ResolvedParameter for unnamed registrations

The UserService constructor has two parameters, a IUnitOfWork and a IUserRepository:
public UserService(IUnitOfWork unitofWork, IUserRepository userRepository)
{ ... }
I am using named registrations to differentiate between multiple instances of IUnitOfWork, so when registering the UserService with the Unity container, I need to explicitly specify the parameters using an InjectionConstructor:
container.RegisterType<IUserService, UserService>(
new InjectionConstructor(
new ResolvedParameter<IUnitOfWork>("someContext"),
new ResolvedParameter<IUserRepository>()
)
);
Is it possible for new ResolvedParameter<IUserRepository>() to be omitted? I would like Unity to implicitly deduce this parameter since there is no need for a named registration. The code would look like this:
container.RegisterType<IUserService, UserService>(
new InjectionConstructor(
new ResolvedParameter<IUnitOfWork>("someContext")
)
);
This would be done is any case when I don't need to use the InjectionConstructor.
Based on InjectionConstructor, I came up with this RequiredInjectionConstructor. It allows you to specify any set of arguments and it will attempt to find a constructor which is required to have (at a minimum) the passed set of injection parameters. If there are multiple constructors that meet this criteria, it chooses the constructor with the least number of parameters. The remaining constructor parameters are assumed to be unnamed resolved parameters.
I haven't performed a full suite of unit tests on it yet, so let me know if you encounter any issues.
/// <summary>
/// A class that holds the collection of minimum required
/// parameters for a constructor, so that the container can
/// be configured to call this constructor.
/// </summary>
public class RequiredInjectionConstructor : InjectionMember
{
private readonly List<InjectionParameterValue> _requiredParameterValues;
/// <summary>
/// Create a new instance of <see cref="RequiredInjectionConstructor"/> that looks
/// for a constructor with a minimum of the given required set of parameters.
/// </summary>
/// <param name="requiredParameterValues">The values for the parameters, that will
/// be converted to <see cref="InjectionParameterValue"/> objects.</param>
public RequiredInjectionConstructor(params object[] requiredParameterValues)
{
_requiredParameterValues = InjectionParameterValue.ToParameters(requiredParameterValues).ToList();
}
/// <summary>
/// Add policies to the <paramref name="policies"/> to configure the
/// container to call this constructor with the required parameter values.
/// </summary>
/// <param name="serviceType">Interface registered, ignored in this implementation.</param>
/// <param name="implementationType">Type to register.</param>
/// <param name="name">Name used to resolve the type object.</param>
/// <param name="policies">Policy list to add policies to.</param>
public override void AddPolicies(Type serviceType, Type implementationType, string name, IPolicyList policies)
{
ConstructorInfo ctor = FindConstructor(implementationType, _requiredParameterValues);
IEnumerable<InjectionParameterValue> selectedConstructorParameterValues = GetSelectedConstructorParameterValues(ctor, _requiredParameterValues);
policies.Set<IConstructorSelectorPolicy>(
new SpecifiedConstructorSelectorPolicy(ctor, selectedConstructorParameterValues.ToArray()),
new NamedTypeBuildKey(implementationType, name));
}
private static ConstructorInfo FindConstructor(Type typeToCreate, IEnumerable<InjectionParameterValue> requiredInjectionParameters)
{
var typeToCreateReflector = new ReflectionHelper(typeToCreate);
var matchedConstructors = typeToCreateReflector.InstanceConstructors.
Where(ctor =>
{
var constructorParameterTypes = ctor.GetParameters().Select(info => info.ParameterType);
return requiredInjectionParameters.All(required => constructorParameterTypes.Any(required.MatchesType));
});
if (matchedConstructors.Any())
{
// Prefer the constructor that has the least number of arguments.
// Other preference models could be implemented here.
return matchedConstructors.OrderBy(ctor =>
ctor.GetParameters().Count()).
FirstOrDefault();
}
string signature = string.Join(", ", requiredInjectionParameters.Select(required => required.ParameterTypeName).ToArray());
throw new InvalidOperationException(
string.Format("Unable to find a constructor with the minimum required parameters. Type: {0}, RequiredParameters: {1}",
typeToCreate.FullName,
signature));
}
private static IEnumerable<InjectionParameterValue> GetSelectedConstructorParameterValues(ConstructorInfo ctor, IEnumerable<InjectionParameterValue> requiredInjectionParameters)
{
var injectionParameterValues = new List<InjectionParameterValue>();
foreach (var parameter in ctor.GetParameters())
{
var existingInjectionParameter = requiredInjectionParameters.FirstOrDefault(required => required.MatchesType(parameter.ParameterType));
injectionParameterValues.Add(existingInjectionParameter ?? new ResolvedParameter(parameter.ParameterType));
}
return injectionParameterValues;
}
}
Would you be willing to decorate your constructor with the DependencyAttribute from Unity? This solution is straight forward, built-in, and lets you pick and chose named dependency. But it does 'dirty' your constructor with Unity goo.
public UserService(
[Dependency("someContext")]IUnitOfWork unitofWork,
IUserRepository userRepository)
{ ... }
Another solution would be to write a custom BuilderStrategy and UnityContainerExtension. This could be done with a bit more work.

Using MvcContrib TestHelper to assert that an inbound route should not be mapped

Looked for a method on the MvcContrib.TestHelper.RouteTestingExtensions class named ShouldNotMap. There is ShouldBeIgnored, but I don't want to test an IgnoreRoute invocation. I want to test that a specific incoming route should not be mapped to any resource.
Is there a way to do this using MvcContrib TestHelper?
Update
Just tried this, and it seems to work. Is this the correct way?
"~/do/not/map/this".Route().ShouldBeNull();
I think you are looking for the following:
"~/do/not/map/this".ShouldBeIgnored();
Behind the scenes this asserts that the route is processed by StopRoutingHandler.
I was looking for the same thing. I ended up adding the following extension methods to implement ShouldBeNull and the even shorter ShouldNotMap:
In RouteTestingExtensions.cs:
/// <summary>
/// Verifies that no corresponding route is defined.
/// </summary>
/// <param name="relativeUrl"></param>
public static void ShouldNotMap(this string relativeUrl)
{
RouteData routeData = relativeUrl.Route();
routeData.ShouldBeNull(string.Format("URL '{0}' shouldn't map.", relativeUrl));
}
/// <summary>
/// Verifies that the <see cref="RouteData">routeData</see> is null.
/// </summary>
public static void ShouldNotMap(this RouteData routeData)
{
routeData.ShouldBeNull("URL should not map.");
}
In GeneralTestExtensions.cs :
///<summary>
/// Asserts that the object should be null.
///</summary>
///<param name="actual"></param>
///<param name="message"></param>
///<exception cref="AssertFailedException"></exception>
public static void ShouldBeNull(this object actual, string message)
{
if (actual != null)
{
throw new AssertFailedException(message);
}
}

ASP.NET MVC: How can I get the browser to open and display a PDF instead of displaying a download prompt?

Ok, so I have an action method that generates a PDF and returns it to the browser. The problem is that instead of automatically opening the PDF, IE displays a download prompt even though it knows what kind of file it is. Chrome does the same thing. In both browsers if I click a link to a PDF file that is stored on a server it will open up just fine and never display a download prompt.
Here is the code that is called to return the PDF:
public FileResult Report(int id)
{
var customer = customersRepository.GetCustomer(id);
if (customer != null)
{
return File(RenderPDF(this.ControllerContext, "~/Views/Forms/Report.aspx", customer), "application/pdf", "Report - Customer # " + id.ToString() + ".pdf");
}
return null;
}
Here's the response header from the server:
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Thu, 16 Sep 2010 06:14:13 GMT
X-AspNet-Version: 4.0.30319
X-AspNetMvc-Version: 2.0
Content-Disposition: attachment; filename="Report - Customer # 60.pdf"
Cache-Control: private, s-maxage=0
Content-Type: application/pdf
Content-Length: 79244
Connection: Close
Do I have to add something special to the response to get the browser to open the PDF automatically?
Any help is greatly appreciated! Thanks!
Response.AppendHeader("Content-Disposition", "inline; filename=foo.pdf");
return File(...
On the HTTP level your 'Content-Disposition' header should have 'inline' not 'attachment'.
Unfortunately, that's not supported by the FileResult (or it's derived classes) directly.
If you're already generating the document in a page or handler you could simply redirect the browser there. If that's not what you want you could subclass the FileResult and add support for streaming documents inline.
public class CustomFileResult : FileContentResult
{
public CustomFileResult( byte[] fileContents, string contentType ) : base( fileContents, contentType )
{
}
public bool Inline { get; set; }
public override void ExecuteResult( ControllerContext context )
{
if( context == null )
{
throw new ArgumentNullException( "context" );
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = ContentType;
if( !string.IsNullOrEmpty( FileDownloadName ) )
{
string str = new ContentDisposition { FileName = this.FileDownloadName, Inline = Inline }.ToString();
context.HttpContext.Response.AddHeader( "Content-Disposition", str );
}
WriteFile( response );
}
}
A simpler solution is not to specify the filename on the Controller.File method. This way you will not get the ContentDisposition header, which means you loose the file name hint when saving the PDF.
I had same issue,but none of the solutions not worked in Firefox until I changed the Options of my browser. In Options
window,then Application Tab change the Portable Document Format to Preview in Firefox.
I use following classes for having more options with content-disposition header.
It works quite like Marnix answer, but instead of fully generating the header with the ContentDisposition class, which unfortunately does not comply to RFC when file name has to be utf-8 encoded, it tweaks instead the header generated by MVC, which complies to RFC.
(Originally, I have written that in part using this response to another question and this another one.)
using System;
using System.IO;
using System.Web;
using System.Web.Mvc;
namespace Whatever
{
/// <summary>
/// Add to FilePathResult some properties for specifying file name without forcing a download and specifying size.
/// And add a workaround for allowing error cases to still display error page.
/// </summary>
public class FilePathResultEx : FilePathResult
{
/// <summary>
/// In case a file name has been supplied, control whether it should be opened inline or downloaded.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool Inline { get; set; }
/// <summary>
/// Whether file size should be indicated or not.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool IncludeSize { get; set; }
public FilePathResultEx(string fileName, string contentType) : base(fileName, contentType) { }
public override void ExecuteResult(ControllerContext context)
{
FileResultUtils.ExecuteResultWithHeadersRestoredOnFailure(context, base.ExecuteResult);
}
protected override void WriteFile(HttpResponseBase response)
{
if (Inline)
FileResultUtils.TweakDispositionAsInline(response);
// File.Exists is more robust than testing through FileInfo, especially in case of invalid path: it does yield false rather than an exception.
// We wish not to crash here, in order to let FilePathResult crash in its usual way.
if (IncludeSize && File.Exists(FileName))
{
var fileInfo = new FileInfo(FileName);
FileResultUtils.TweakDispositionSize(response, fileInfo.Length);
}
base.WriteFile(response);
}
}
/// <summary>
/// Add to FileStreamResult some properties for specifying file name without forcing a download and specifying size.
/// And add a workaround for allowing error cases to still display error page.
/// </summary>
public class FileStreamResultEx : FileStreamResult
{
/// <summary>
/// In case a file name has been supplied, control whether it should be opened inline or downloaded.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool Inline { get; set; }
/// <summary>
/// If greater than <c>0</c>, the content size to include in content-disposition header.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public long Size { get; set; }
public FileStreamResultEx(Stream fileStream, string contentType) : base(fileStream, contentType) { }
public override void ExecuteResult(ControllerContext context)
{
FileResultUtils.ExecuteResultWithHeadersRestoredOnFailure(context, base.ExecuteResult);
}
protected override void WriteFile(HttpResponseBase response)
{
if (Inline)
FileResultUtils.TweakDispositionAsInline(response);
FileResultUtils.TweakDispositionSize(response, Size);
base.WriteFile(response);
}
}
/// <summary>
/// Add to FileContentResult some properties for specifying file name without forcing a download and specifying size.
/// And add a workaround for allowing error cases to still display error page.
/// </summary>
public class FileContentResultEx : FileContentResult
{
/// <summary>
/// In case a file name has been supplied, control whether it should be opened inline or downloaded.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool Inline { get; set; }
/// <summary>
/// Whether file size should be indicated or not.
/// </summary>
/// <remarks>If <c>FileDownloadName</c> is <c>null</c> or empty, this property has no effect (due to current implementation).</remarks>
public bool IncludeSize { get; set; }
public FileContentResultEx(byte[] fileContents, string contentType) : base(fileContents, contentType) { }
public override void ExecuteResult(ControllerContext context)
{
FileResultUtils.ExecuteResultWithHeadersRestoredOnFailure(context, base.ExecuteResult);
}
protected override void WriteFile(HttpResponseBase response)
{
if (Inline)
FileResultUtils.TweakDispositionAsInline(response);
if (IncludeSize)
FileResultUtils.TweakDispositionSize(response, FileContents.LongLength);
base.WriteFile(response);
}
}
public static class FileResultUtils
{
public static void ExecuteResultWithHeadersRestoredOnFailure(ControllerContext context, Action<ControllerContext> executeResult)
{
if (context == null)
throw new ArgumentNullException("context");
if (executeResult == null)
throw new ArgumentNullException("executeResult");
var response = context.HttpContext.Response;
var previousContentType = response.ContentType;
try
{
executeResult(context);
}
catch
{
if (response.HeadersWritten)
throw;
// Error logic will usually output a content corresponding to original content type. Restore it if response can still be rewritten.
// (Error logic should ensure headers positionning itself indeed... But this is not the case at least with HandleErrorAttribute.)
response.ContentType = previousContentType;
// If a content-disposition header have been set (through DownloadFilename), it must be removed too.
response.Headers.Remove(ContentDispositionHeader);
throw;
}
}
private const string ContentDispositionHeader = "Content-Disposition";
// Unfortunately, the content disposition generation logic is hidden in an Mvc.Net internal class, while not trivial (UTF-8 support).
// Hacking it after its generation.
// Beware, do not try using System.Net.Mime.ContentDisposition instead, it does not conform to the RFC. It does some base64 UTF-8
// encoding while it should append '*' to parameter name and use RFC 5987 encoding. https://www.rfc-editor.org/rfc/rfc6266#section-4.3
// And https://stackoverflow.com/a/22221217/1178314 comment.
// To ask for a fix: https://github.com/aspnet/Mvc
// Other class : System.Net.Http.Headers.ContentDispositionHeaderValue looks better. But requires to detect if the filename needs encoding
// and if yes, use the 'Star' suffixed property along with setting the sanitized name in non Star property.
// MVC 6 relies on ASP.NET 5 https://github.com/aspnet/HttpAbstractions which provide a forked version of previous class, with a method
// for handling that: https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.Net.Http.Headers/ContentDispositionHeaderValue.cs
// MVC 6 stil does not give control on FileResult content-disposition header.
public static void TweakDispositionAsInline(HttpResponseBase response)
{
var disposition = response.Headers[ContentDispositionHeader];
const string downloadModeToken = "attachment;";
if (string.IsNullOrEmpty(disposition) || !disposition.StartsWith(downloadModeToken, StringComparison.OrdinalIgnoreCase))
return;
response.Headers.Remove(ContentDispositionHeader);
response.Headers.Add(ContentDispositionHeader, "inline;" + disposition.Substring(downloadModeToken.Length));
}
public static void TweakDispositionSize(HttpResponseBase response, long size)
{
if (size <= 0)
return;
var disposition = response.Headers[ContentDispositionHeader];
const string sizeToken = "size=";
// Due to current ancestor semantics (no file => inline, file name => download), handling lack of ancestor content-disposition
// is non trivial. In this case, the content is by default inline, while the Inline property is <c>false</c> by default.
// This could lead to an unexpected behavior change. So currently not handled.
if (string.IsNullOrEmpty(disposition) || disposition.Contains(sizeToken))
return;
response.Headers.Remove(ContentDispositionHeader);
response.Headers.Add(ContentDispositionHeader, disposition + "; " + sizeToken + size.ToString());
}
}
}
Sample usage:
public FileResult Download(int id)
{
// some code to get filepath and filename for browser
...
return
new FilePathResultEx(filepath, System.Web.MimeMapping.GetMimeMapping(filename))
{
FileDownloadName = filename,
Inline = true
};
}
Note that specifying a file name with Inline will not work with Internet Explorer (11 included, Windows 10 Edge included, tested with some pdf files), while it works with Firefox and Chrome. Internet Explorer will ignore the file name. For Internet Explorer, you need to hack your url path, which is quite bad imo. See this answer.
Just return a FileStreamResult instead of File
And make sure you don't wrap your new FileStreamResult in a File at the end. Just return the FileStreamResult as it is. And probably you need to modify the return type of the action also to FileSteamResult

Resources