Inject value from middleware into Razor Page HTML body - asp.net-core

I have a middleware which calculates Razor Page execution time:
app.Use(async (context, next) =>
{
var sw = new Stopwatch();
sw.Start();
await next.Invoke();
sw.Stop();
await context.Response.WriteAsync($"<div class=\"container\">Processing time: {sw.ElapsedMilliseconds} ms<div>");
});
It works fine, however HTML created by WriteAsync is added at the very end of the response, after closing </html> tag.
How can I place - pass or inject - a value of sw.ElapsedMilliseconds calculated by the middleware in specific place inside HTML body? Using ASP.NET Core 6.0.

Maybe you can use the HTML Agility Pack.
You can use the LoadHtml and HtmlNode.CreateNode methods to create the html element you want to insert, and then use DocumentNode.SelectSingleNode to select the position you want to insert.
For testing,you can refer to the following code:
Custom middleware:
//Install-Package HtmlAgilityPack
public class ResponseMeasurementMiddleware
{
private readonly RequestDelegate _next;
public ResponseMeasurementMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var originalBody = context.Response.Body;
var newBody = new MemoryStream();
context.Response.Body = newBody;
var watch = new Stopwatch();
long responseTime = 0;
watch.Start();
await _next(context);
// read the new body
responseTime = watch.ElapsedMilliseconds;
newBody.Position = 0;
var newContent = await new StreamReader(newBody).ReadToEndAsync();
// calculate the updated html
var updatedHtml = CreateDataNode(newContent, responseTime);
// set the body = updated html
var updatedStream = GenerateStreamFromString(updatedHtml);
await updatedStream.CopyToAsync(originalBody);
context.Response.Body = originalBody;
}
public static Stream GenerateStreamFromString(string s)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
private string CreateDataNode(string originalHtml, long responseTime)
{
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(originalHtml);
HtmlNode testNode = HtmlNode.CreateNode($"<div class=\"container\">Processing time: {responseTime.ToString()} ms.</div>");
var htmlBody = htmlDoc.DocumentNode.SelectSingleNode(".//body/div");
if (htmlBody != null)
{
htmlBody.InsertBefore(testNode, htmlBody.FirstChild);
}
string rawHtml = htmlDoc.DocumentNode.OuterHtml; //using this results in a page that displays my inserted HTML correctly, but duplicates the original page content.
//rawHtml = "some text"; uncommenting this results in a page with the correct format: this text, followed by the original contents of the page
return rawHtml;
}
}
htmlDoc.DocumentNode.SelectSingleNode(".//body/div") and htmlBody.InsertBefore(testNode, htmlBody.FirstChild) can customize where you want to insert.
And use custom middleware:
app.UseMiddleware<ResponseMeasurementMiddleware>();
Test Result:

Related

Reading and DeserializeObject Response Header using Asp Core 3.1

I'm try to make a paging for collection, I have a Web API and I'm sending paging data into header like total count and next page link so I can read the response header and put it into string as a Json here is my consume code
public async Task<MultibleValuesHelper> GetAllTags()
{
IEnumerable<TagModelDto> tags;
IEnumerable<PagedDataModel> Header=null;
using (var response = await _httpClient.GetAsync($"tag"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
if( response.Headers.TryGetValues("X-Pagenation", out var Pagenation))
{
var TryHeader = Pagenation.FirstOrDefault();
Header = JsonConvert.DeserializeObject<List<PagedDataModel>>(TryHeader);
}
tags = JsonConvert.DeserializeObject<List<TagModelDto>>(apiResponse);
}
return new MultibleValuesHelper {
TagServiceCollection = tags,
HeaderPagenation = Header
};
}
my problem when I'm try to Deserialize header it showen Error :JsonSerializationException: Cannot deserialize the current JSON object So how I can Deserialize Header and put it into IEnumerable Header so I can handle it
Okay the answer is a little bit change in the code we will use PagedDataModel instead List, and here is the full code
public async Task<MultibleValuesHelper> GetAllTags()
{
IEnumerable<TagModelDto> tags;
PagedDataModel Header=new PagedDataModel ;
using (var response = await _httpClient.GetAsync($"tag"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
if( response.Headers.TryGetValues("X-Pagenation", out var Pagenation))
{
var TryHeader = Pagenation.FirstOrDefault();
Header = JsonConvert.DeserializeObject<PagedDataModel>(TryHeader);
}
tags = JsonConvert.DeserializeObject<List<TagModelDto>>(apiResponse);
}
return new MultibleValuesHelper {
TagServiceCollection = tags,
HeaderPagenation = Header
};
}
this is will work Thanks for all of you

How to call API method that accepts XML in ASP.NET Core Web APIs?

The API Method I am calling is:
[HttpPost("PostXml")]
[Consumes("application/xml")]
[Produces("application/xml")]
public Reservation PostXml([FromBody] Reservation res) =>
repository.AddReservation(new Reservation
{
Name = res.Name,
StartLocation = res.StartLocation,
EndLocation = res.EndLocation
});
The client code:
[HttpPost]
public async Task<IActionResult> AddReservationByXml(Reservation reservation)
{
Reservation receivedReservation = new Reservation();
using (var httpClient = new HttpClient())
{
StringContent content = new StringContent(ConvertObjectToXMLString(reservation), Encoding.UTF8, "application/xml");
using (var response = await httpClient.PostAsync("http://localhost:8888/api/Reservation/PostXml", content))
{
string apiResponse = await response.Content.ReadAsStringAsync();
receivedReservation = JsonConvert.DeserializeObject<Reservation>(apiResponse);
}
}
return View(receivedReservation);
}
string ConvertObjectToXMLString(object classObject)
{
string xmlString = null;
XmlSerializer xmlSerializer = new XmlSerializer(classObject.GetType());
using (MemoryStream memoryStream = new MemoryStream())
{
xmlSerializer.Serialize(memoryStream, classObject);
memoryStream.Position = 0;
xmlString = new StreamReader(memoryStream).ReadToEnd();
}
return xmlString;
}
I am failing to call the API method and getting the error:
400One or
more validation errors
occurred.https://tools.ietf.org/html/rfc7231#section-6.5.1|f5452728-4c49abe4d88e559e.1.8095e7c1_An
error occurred while deserializing input
data.
What is wrong here?
You got error from this line
receivedReservation = JsonConvert.DeserializeObject<Reservation>(apiResponse);
Please check the validation of your Reservation Model and detail the values you past
and the content of error you got.
Your XML request should be with the correct case as the c# class. XML is case sensitive.
Try this code.
XmlSerializer xsSubmit = new XmlSerializer(typeof(Reservation));
System.IO.StringWriter sww = new System.IO.StringWriter();
XmlWriter writer = XmlWriter.Create(sww);
xsSubmit.Serialize(writer, reservation);
var xml = sww.ToString();

Asp.Net Core - Making API calls from backend

I have an application which is calling API's from a backend cs class, using IHostedService. With basic API calls ("http://httpbin.org/ip") it is working fine and returning the correct value, however I now need to call a Siemens API which requires me to set an Authorization header, and place "grant_type=client_credentials" in the body.
public async Task<string> GetResult()
{
string data = "";
string baseUrl = "https://<space-name>.mindsphere.io/oauth/token";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", {ServiceCredentialID: ServiceCredentialSecret});
using (HttpResponseMessage res = await client.GetAsync(baseUrl))
{
using (HttpContent content = res.Content)
{
data = await content.ReadAsStringAsync();
}
}
}
I think I have the header set up correctly but I won't know for sure until the full request gets formatted. Is it even possible to set the the body of the request to "grant_type=client_credentials"?
As far as I can see from Siemens API documentation they expect Form data, so it should be like:
public async Task<string> GetResult()
{
string data = "";
string baseUrl = "https://<space-name>.mindsphere.io/oauth/token";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", {ServiceCredentialID: ServiceCredentialSecret});
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
});
using (HttpResponseMessage res = await client.PostAsync(baseUrl, formContent))
{
using (HttpContent content = res.Content)
{
data = await content.ReadAsStringAsync();
}
}
}
}

JsReport .NET Core - Generate PDF from url

Trying to use JsReport to generate a pdf from a url but can't find any documentation or examples in their github repo.
Basically I need to generate the pdf and attach it to an email and I've managed to get data back as a byte[], but I can't seem to figure out how to use an existing View/Action.
This is the action that generates the PDF for viewing...
[MiddlewareFilter(typeof(JsReportPipeline))]
public async Task<IActionResult> Pdf(Guid id)
{
var serviceOrder = await _serviceOrderService.Get(id);
if (serviceOrder == null) return new NotFoundResult();
var model = _mapper.Map<ServiceOrderModel>(serviceOrder);
HttpContext.JsReportFeature().Recipe(Recipe.PhantomPdf);
return View(model);
}
This action should take the pdf view from "Details" and generate a PDF that I can attach. Below I can generate it with static content like "Hello from pdf" but I can't figure out how to use my "Details" view in ASPNET Core.
public async Task<IActionResult> Email(Guid id)
{
var rs = new LocalReporting().UseBinary(JsReportBinary.GetBinary()).AsUtility().Create();
var report = await rs.RenderAsync(new RenderRequest()
{
Template = new Template()
{
Recipe = Recipe.PhantomPdf,
Engine = Engine.None,
Content = "Hello from pdf",
}
});
var memoryStream = new MemoryStream();
await report.Content.CopyToAsync(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(memoryStream, "application/pdf") { FileDownloadName = "out.pdf" };
}
Taken From the JsReport Github Dotnet Example,
[MiddlewareFilter(typeof(JsReportPipeline))]
public IActionResult InvoiceDownload()
{
HttpContext.JsReportFeature().Recipe(Recipe.ChromePdf)
.OnAfterRender((r) => HttpContext.Response.Headers["Content-Disposition"] = "attachment; filename=\"myReport.pdf\"");
return View("Invoice", InvoiceModel.Example());
}
If you want to return a file from Asp.net Core Controller Action method then try the following
[MiddlewareFilter(typeof(JsReportPipeline))]
public async Task<IActionResult> Pdf(Guid id)
{
var serviceOrder = await _serviceOrderService.Get(id);
if (serviceOrder == null) return new NotFoundResult();
var model = _mapper.Map<ServiceOrderModel>(serviceOrder);
HttpContext.JsReportFeature().Recipe(Recipe.PhantomPdf).OnAfterRender((r) =>
HttpContext.Response.Headers["Content-Disposition"] = "attachment; filename=\"out.pdf\"");
return View(model);
}

ASP.NET Core Compression Middleware - Empty Reponse

I am using some custom compression middleware from this repository (pasted below). Upon the first request, the content is compressed just fine. For every request after that, the response comes back as completely empty (with a Content-Length of 0).
This only started happening after migrating from ASP.NET Core RC2 to RTM.
Does anyone know why this is happening?
CompressionMiddleware:
public class CompressionMiddleware
{
private readonly RequestDelegate _next;
public CompressionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (IsGZipSupported(context))
{
string acceptEncoding = context.Request.Headers["Accept-Encoding"];
var buffer = new MemoryStream();
var stream = context.Response.Body;
context.Response.Body = buffer;
await _next(context);
if (acceptEncoding.Contains("gzip"))
{
var gstream = new GZipStream(stream, CompressionLevel.Optimal);
context.Response.Headers.Add("Content-Encoding", new[] { "gzip" });
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(gstream);
gstream.Dispose();
}
else
{
var gstream = new DeflateStream(stream, CompressionLevel.Optimal);
context.Response.Headers.Add("Content-Encoding", new[] { "deflate" });
buffer.Seek(0, SeekOrigin.Begin);
await buffer.CopyToAsync(gstream);
gstream.Dispose();
}
}
else
{
await _next(context);
}
}
public bool IsGZipSupported(HttpContext context)
{
string acceptEncoding = context.Request.Headers["Accept-Encoding"];
return !string.IsNullOrEmpty(acceptEncoding) &&
(acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate"));
}
}
I have found the following in "Add HTTP compression middleware" issue:
I have added gzip and it worked, but first request. I mean in the first request, the response page is null (context.Response.Body) but when you refresh the page (just once) it works correctly after that.(I don't know why but I have to solve it)
And response on question is:
You need to update
context.Response.Headers["Content-Length"] with actual compressed
buffer length.
CompressionMiddleware.cs
And above link to realisation of compression middleware contains:
if (context.Response.Headers["Content-Length"].Count > 0)
{
context.Response.Headers["Content-Length"] = compressed.Length.ToString();
}