Implementing "Upload large files with streaming" (https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0) gives me this error:
Unexpected end of Stream, the content may have already been read by
another component.
It happens when executing reader.ReadNextSectionAsync():
[HttpPost]
[DisableFormValueModelBinding]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhysical()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
After hours of trying to find an answer i decided to post the issue here.
The DisableFormValueModelBinding filter won't solve this issue.
public void OnResourceExecuting(ResourceExecutingContext context)
{
var factories = context.ValueProviderFactories;
factories.RemoveType<FormValueProviderFactory>();
factories.RemoveType<FormFileValueProviderFactory>();
factories.RemoveType<JQueryFormValueProviderFactory>();
}
I am using .NET 5.0, with a Razor and Mvc Controller application.
Today had kind of this excersice. The same approach worked for me. The key was to remove all the three providers; in the MS sample only two removed.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
var formValueProviderFactory = context.ValueProviderFactories
.OfType<FormValueProviderFactory>()
.FirstOrDefault();
if (formValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(formValueProviderFactory);
}
var jqueryFormValueProviderFactory = context.ValueProviderFactories
.OfType<JQueryFormValueProviderFactory>()
.FirstOrDefault();
if (jqueryFormValueProviderFactory != null)
{
context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
}
var formFileProviderFactory = context.ValueProviderFactories
.OfType<FormFileValueProviderFactory>()
.FirstOrDefault();
if (formFileProviderFactory != null)
{
context.ValueProviderFactories.Remove(formFileProviderFactory);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
and then
[DisableFormValueModelBinding]
public async Task<IActionResult> CreateOrUpdateResource(
[FromQuery] string session)
{
//..
}
Related
I'm using ApplicationInsights and I want to add the request, and after that the response, to the logging properties.
To achieve this I am implementing my own ITelemetryInitializer. It looks exactly like this.
public class MyInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null || _httpContextAccessor?.HttpContext?.Request == null
|| requestTelemetry.Properties.ContainsKey("RequestBody"))
{
return;
}
var request = _httpContextAccessor?.HttpContext?.Request;
request?.EnableRewind();
if (request.Method.Equals(HttpMethod.Post.ToString(), StringComparison.InvariantCultureIgnoreCase)
|| request.Method.Equals(HttpMethod.Put.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
{
var requestBody = reader.ReadToEnd();
requestTelemetry.Properties.Add("RequestBody", requestBody);
}
}
}
}
In startup I've added this
services.AddHttpContextAccessor();
services.AddSingleton<ITelemetryInitializer, MyInitializer>();
services.AddApplicationInsightsTelemetry();
The error I get is:
ObjectDisposedException: Cannot access a disposed object.
Object name: FileBufferingReadStream.
Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream.ThrowIfDisposed()
I've used .EnableRewind as well as instructing the StreamReader to leave the file open. Despite this my request is still null when it actually hits my controller, or even when it hits my initializer again for a second pass (setting the response information).
Any suggestions are welcome.
Additionally I tried adding a piece of middleware to ensure .EnableRewind was on for everything, but this did nothing. I'd prefer not to have to add any additional middleware since I'd like there to be no other dependencies.
app.Use(async (context, next) =>
{
context.Request.EnableRewind();
await next();
});
Thanks.
As always the solution ends up being a single line of code. I owe Mr Gunnar Peipman a thanks for his blog post Reading request body in ASP.NET Core.
The line:
request.Body.Seek(0, SeekOrigin.Begin);
The code
public class MyInitializer : ITelemetryInitializer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public MyInitializer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null || _httpContextAccessor?.HttpContext?.Request == null
|| requestTelemetry.Properties.ContainsKey("RequestBody"))
{
return;
}
var request = _httpContextAccessor?.HttpContext?.Request;
request?.EnableRewind();
if (request.Method.Equals(HttpMethod.Post.ToString(), StringComparison.InvariantCultureIgnoreCase)
|| request.Method.Equals(HttpMethod.Put.ToString(), StringComparison.InvariantCultureIgnoreCase))
{
using (var reader = new StreamReader(request.Body, Encoding.UTF8, true, 1024, true))
{
var requestBody = reader.ReadToEnd();
request.Body.Seek(0, SeekOrigin.Begin);
requestTelemetry.Properties.Add("RequestBody", requestBody);
}
}
}
}
No matter what I try I cannot seem to remove the ; charset=utf-8 part from my response's Content-Type header.
[HttpGet("~/appid")]
// Doesn't work
//[Produces("application/fido.trusted-apps+json")]
public string GetAppId()
{
// Doesn't work
Response.ContentType = "application/fido.trusted-apps+json";
// Doesn't work
//Response.ContentType = null;
//Response.Headers.Add("Content-Type", "application/fido.trusted-apps+json");
return JsonConvert.SerializeObject(new
{
foo = true
});
}
I always get application/fido.trusted-apps+json; charset=utf-8 when I only want application/fido.trusted-apps+json.
Note: This is to conform with the FIDO AppID and Facet Specification v1.0 for U2F which states:
The response must set a MIME Content-Type of "application/fido.trusted-apps+json".
I went with the following approach, using middleware to replace the header on the way out. Seems kinda hacky to have to use middleware like this:
Middleware
public class AdjustHeadersMiddleware
{
private readonly RequestDelegate _next;
public AdjustHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext, CurrentContext currentContext)
{
httpContext.Response.OnStarting((state) =>
{
if(httpContext.Response.Headers.Count > 0 && httpContext.Response.Headers.ContainsKey("Content-Type"))
{
var contentType = httpContext.Response.Headers["Content-Type"].ToString();
if(contentType.StartsWith("application/fido.trusted-apps+json"))
{
httpContext.Response.Headers.Remove("Content-Type");
httpContext.Response.Headers.Append("Content-Type", "application/fido.trusted-apps+json");
}
}
return Task.FromResult(0);
}, null);
await _next.Invoke(httpContext);
}
}
Startup.cs Configure()
app.UseMiddleware<AdjustHeadersMiddleware>();
I have found that you can use ContentResult to override this in your controller. So you could achieve what you want by doing the following for example
string bodyJson = JsonConvert.SerializeObject(new
{
foo = true
})
var response = new ContentResult()
{
Content = bodyJson,
ContentType = "application/fido.trusted-apps+json",
StatusCode = (int)System.Net.HttpStatusCode.OK,
};
return response;
If the system requesting your MVC endpoint sends a proper Accept: application/fido.trusted-apps+json, then I believe a custom formatter is what you're looking for.
See:
ASP.Net Core Custom Formatters (sample code)
Write Your Own ASP.NET Core MVC Formatters
It would look something like this (borrowed from the second link):
public class FidoTrustedAppOutputFormatter : IOutputFormatter
{
public FidoTrustedAppOutputFormatter
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/fido.trusted-apps+json"));
}
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.ContentType == null || context.ContentType.ToString() == "application/fido.trusted-apps+json")
return true;
return false;
}
public async Task WriteAsync(OutputFormatterWriteContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var response = context.HttpContext.Response; response.ContentType = "application/fido.trusted-apps+json";
using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
{
// replace with Json.net implementation
Jil.JSON.Serialize(context.Object, writer);
await writer.FlushAsync();
}
}
}
public class FidoTrustedAppInputFormatter : IInputFormatter
{
public FidoTrustedAppInputFormatter
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/fido.trusted-apps+json"));
}
public bool CanRead(OutputFormatterCanWriteContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.ContentType == null || context.ContentType.ToString() == "application/fido.trusted-apps+json")
return true;
return false;
}
public Task<InputFormatterResult> ReadAsync(InputFormatterContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
var request = context.HttpContext.Request; if (request.ContentLength == 0)
{
if (context.ModelType.GetTypeInfo().IsValueType)
return InputFormatterResult.SuccessAsync(Activator.CreateInstance(context.ModelType));
else return InputFormatterResult.SuccessAsync(null);
}
var encoding = Encoding.UTF8;//do we need to get this from the request im not sure yet
using (var reader = new StreamReader(context.HttpContext.Request.Body))
{
var model = Jil.JSON.Deserialize(reader, context.ModelType);
return InputFormatterResult.SuccessAsync(model);
}
}
}
Then register it in your startup:
services.AddMvcCore(options =>
{
options.InputFormatters.Insert(0, new FidoTrustedAppInputFormatter ());
options.OutputFormatters.Insert(0, new FidoTrustedAppOutputFormatter ());
});
I have used http://www.strathweb.com/2013/01/asynchronously-streaming-video-with-asp-net-web-api/ this technique before and worked perfect for async video streaming.
But for ASP.NET Core this way is not working as expected.
By Video streaming class is:
public class VideoStream
{
private readonly string _filename;
public VideoStream(string filename)
{
_filename = filename;
}
public async Task WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
{
try
{
var buffer = new byte[65536];
using (var video = File.Open(_filename, FileMode.Open, FileAccess.Read))
{
var length = (int)video.Length;
var bytesRead = 1;
while (length > 0 && bytesRead > 0)
{
bytesRead = video.Read(buffer, 0, Math.Min(length, buffer.Length));
await outputStream.WriteAsync(buffer, 0, bytesRead);
length -= bytesRead;
}
}
}
catch (Exception)
{ return; }
finally
{
outputStream.Flush();
outputStream.Dispose();
}
}
}
and I have the following Action for video streaming requests:
[HttpGet]
[Route("[action]")]
public IActionResult GetVideo(int id)
{
var fileName = GetVideoFileName(id);
var video = new VideoStream(fileName);
var response = new HttpResponseMessage
{
Content = new PushStreamContent(video.WriteToStream, new MediaTypeHeaderValue("video/mp4"))
};
var objectResult = new ObjectResult(response);
objectResult.ContentTypes.Add(new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("video/mp4"));
return objectResult;
}
Since by default Asp.Net Core doesn't have built-in Media Formatter for video/mp4 I have created the following custom Media Formatter
public class VideoOutputFormatter : IOutputFormatter
{
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
return true;
}
public async Task WriteAsync(OutputFormatterWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
var response = context.HttpContext.Response;
response.ContentType = "video/mp4";
How to impelemnt ???
}
}
and added the following line to Startup.cs
services.AddMvc(options =>
{
options.OutputFormatters.Add(new VideoOutputFormatter());
});
It actually calls my custom formatter.
I doesn't know how to implement this custom media formatter for video/mp4.
Anyone can help me ?
Looking at the source code for Asp.NET Core really helped me find the answer to this one. They have a StreamOutputFormatter class that's really close to what you want to use. I only had to modify it to look for PushStreamContent and it worked like a charm.
Here's my complete VideoOutputFormatter:
public class VideoOutputFormatter : IOutputFormatter
{
public bool CanWriteResult(OutputFormatterCanWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.Object is PushStreamContent)
return true;
return false;
}
public async Task WriteAsync(OutputFormatterWriteContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
using (var stream = ((PushStreamContent)context.Object))
{
var response = context.HttpContext.Response;
if (context.ContentType != null)
{
response.ContentType = context.ContentType.ToString();
}
await stream.CopyToAsync(response.Body);
}
}
}
Instead of wrapping the HttpResponseMessage in the ObjectResult in your controller, you'll want to just shove the PushStreamContent object into the ObjectResult instead. You still need to set the MediaTypeHeaderValue on the ObjectResult.
I have found this link:
http://giddyrobot.com/preserving-important-comments-in-mvc-4-bundles/
It shows how to do this same thing for JavaScript and I have used it to make an attempt for StyleBundles, but I'm unsure if it's doing things correctly on the backend.
Is the source code available? If not does anyone know if this seems right? All I want to keep is comments that start with /*! so that licenses for open source projects like normalize get included properly in production.
Here is what I have so far:
public static void RegisterBundles(BundleCollection bundles)
{
// Allows us to keep /*! comments for licensing purposes
var cssBundleSettings = new CssSettings
{
CommentMode = CssComment.Important
};
}
public class ConfigurableStyleBundle : Bundle
{
public ConfigurableStyleBundle(string virtualPath, CssSettings cssSettings) :
this(virtualPath, cssSettings, null) { }
public ConfigurableStyleBundle(string virtualPath, CssSettings cssSettings, string cdnPath) :
base(virtualPath, cdnPath, new[] { new ConfigurableCSSTransform(cssSettings) })
{
// commented out from js concatenation token not sure if this one should have one
//base.ConcatenationToken = ";";
}
}
[ExcludeFromCodeCoverage]
public class ConfigurableCSSTransform : IBundleTransform
{
private readonly CssSettings _cssSettings;
public ConfigurableCSSTransform(CssSettings cssSettings)
{
_cssSettings = cssSettings;
}
public void Process(BundleContext context, BundleResponse response)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (response == null)
{
throw new ArgumentNullException("response");
}
if (!context.EnableInstrumentation)
{
var minifier = new Minifier();
var content = minifier.MinifyStyleSheet(response.Content, _cssSettings);
if (minifier.ErrorList.Count > 0)
{
GenerateErrorResponse(response, minifier.ErrorList);
}
else
{
response.Content = content;
}
}
response.ContentType = "text/css";
}
internal static void GenerateErrorResponse(BundleResponse bundle, IEnumerable<object> errors)
{
var content = new StringBuilder();
content.Append("/* ");
content.Append("CSS MinifyError").Append("\r\n");
foreach (object current in errors)
{
content.Append(current.ToString()).Append("\r\n");
}
content.Append(" */\r\n");
content.Append(bundle.Content);
bundle.Content = content.ToString();
}
}
All of this is wrapped in public class BundleConfig and gets called from Global.asax.
I'm just wondering if CssComment.Important could have negative effects and remove too much and if this seems to be doing what I want it to? When I have tested it everything seems to look correct styling wise, but it doesn't hurt to get some eyes seeing as this is probably useful for a lot of other ASP.NET devs who use open source libraries.
I don't think you've done anything incorrectly. Though I would approach it using the IBundleBuilder interface, as this will also keep regular comments out of production from prying eyes who switch user agent, like specified in How to prevent User-Agent: Eureka/1 to return source code. I show some steps on how to test against this in this related blog post.
public class ConfigurableStyleBuilder : IBundleBuilder
{
public virtual string BuildBundleContent(Bundle bundle, BundleContext context, IEnumerable<BundleFile> files)
{
var content = new StringBuilder();
foreach (var file in files)
{
FileInfo f = new FileInfo(HttpContext.Current.Server.MapPath(file.VirtualFile.VirtualPath));
CssSettings settings = new CssSettings();
settings.CommentMode = Microsoft.Ajax.Utilities.CssComment.Important;
var minifier = new Microsoft.Ajax.Utilities.Minifier();
string readFile = Read(f);
string res = minifier.MinifyStyleSheet(readFile, settings);
if (minifier.ErrorList.Count > 0)
{
res = PrependErrors(readFile, minifier.ErrorList);
content.Insert(0, res);
}
else
{
content.Append(res);
}
}
return content.ToString();
}
private string PrependErrors(string file, ICollection<ContextError> errors )
{
var content = new StringBuilder();
content.Append("/* ");
content.Append("CSS MinifyError").Append("\r\n");
foreach (object current in errors)
{
content.Append(current.ToString()).Append("\r\n");
}
content.Append("Minify Error */\r\n");
content.Append(file);
return content.ToString();
}
private string Read(FileInfo file)
{
using (var r = file.OpenText())
{
return r.ReadToEnd();
}
}
}
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
var cssBundle = new ConfigurableStyleBundle("~/Content/css");
cssBundle.Include("~/Content/stylesheet1.css");
cssBundle.Include("~/Content/stylesheet2.css");
bundles.Add(cssBundle);
//etc
}
}
I made a NuGet package for this (including a version for scripts) - https://www.nuget.org/packages/LicensedBundler/
"How can i use engine in my console application"
I shouldn't use the ITemplate-interface and Transform-Method.
I am using Tridion 2011
Could anyone please suggest me.
You can't. The Engine class is part of the TOM.NET and that API is explicitly reserved for use in:
Template Building Blocks
Event Handlers
For all other cases (such as console applications) you should use the Core Service.
There are many good questions (and articles on other web sites) already:
https://stackoverflow.com/search?q=%5Btridion%5D+core+service
http://www.google.com/#q=tridion+core+service
If you get stuck along the way, show us the relevant code+configuration you have and what error message your get (or at what step you are stuck) and we'll try to help from there.
From a console application you should use the Core Service. I wrote a small example using the Core Service to search for items in the content manager.
Console.WriteLine("FullTextQuery:");
var fullTextQuery = Console.ReadLine();
if (String.IsNullOrWhiteSpace(fullTextQuery) || fullTextQuery.Equals(":q", StringComparison.OrdinalIgnoreCase))
{
break;
}
Console.WriteLine("SearchIn IdRef:");
var searchInIdRef = Console.ReadLine();
var queryData = new SearchQueryData
{
FullTextQuery = fullTextQuery,
SearchIn = new LinkToIdentifiableObjectData
{
IdRef = searchInIdRef
}
};
var results = coreServiceClient.GetSearchResults(queryData);
results.ToList().ForEach(result => Console.WriteLine("{0} ({1})", result.Title, result.Id));
Add a reference to Tridion.ContentManager.CoreService.Client to your Visual Studio Project.
Code of the Core Service Client Provider:
public interface ICoreServiceProvider
{
CoreServiceClient GetCoreServiceClient();
}
public class CoreServiceDefaultProvider : ICoreServiceProvider
{
private CoreServiceClient _client;
public CoreServiceClient GetCoreServiceClient()
{
return _client ?? (_client = new CoreServiceClient());
}
}
And the client itself:
public class CoreServiceClient : IDisposable
{
public SessionAwareCoreServiceClient ProxyClient;
private const string DefaultEndpointName = "netTcp_2011";
public CoreServiceClient(string endPointName)
{
if(string.IsNullOrWhiteSpace(endPointName))
{
throw new ArgumentNullException("endPointName", "EndPointName is not specified.");
}
ProxyClient = new SessionAwareCoreServiceClient(endPointName);
}
public CoreServiceClient() : this(DefaultEndpointName) { }
public string GetApiVersionNumber()
{
return ProxyClient.GetApiVersion();
}
public IdentifiableObjectData[] GetSearchResults(SearchQueryData filter)
{
return ProxyClient.GetSearchResults(filter);
}
public IdentifiableObjectData Read(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
public ApplicationData ReadApplicationData(string subjectId, string applicationId)
{
return ProxyClient.ReadApplicationData(subjectId, applicationId);
}
public void Dispose()
{
if (ProxyClient.State == CommunicationState.Faulted)
{
ProxyClient.Abort();
}
else
{
ProxyClient.Close();
}
}
}
When you want to perform CRUD actions through the core service you can implement the following methods in the client:
public IdentifiableObjectData CreateItem(IdentifiableObjectData data)
{
data = ProxyClient.Create(data, new ReadOptions());
return data;
}
public IdentifiableObjectData UpdateItem(IdentifiableObjectData data)
{
data = ProxyClient.Update(data, new ReadOptions());
return data;
}
public IdentifiableObjectData ReadItem(string id)
{
return ProxyClient.Read(id, new ReadOptions());
}
To construct a data object of e.g. a Component you can implement a Component Builder class that implements a create method that does this for you:
public ComponentData Create(string folderUri, string title, string content)
{
var data = new ComponentData()
{
Id = "tcm:0-0-0",
Title = title,
Content = content,
LocationInfo = new LocationInfo()
};
data.LocationInfo.OrganizationalItem = new LinkToOrganizationalItemData
{
IdRef = folderUri
};
using (CoreServiceClient client = provider.GetCoreServiceClient())
{
data = (ComponentData)client.CreateItem(data);
}
return data;
}
Hope this gets you started.