WebAPI can't call Put method - c#-4.0

I have the following code on server:
public class UploadController : ApiController
{
public void Put(string filename, string description)
{
...
}
public void Put()
{
...
}
and try to call it from client:
var clientDescr = new HttpClient();
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("filename", "test"));
postData.Add(new KeyValuePair<string, string>("description", "100"));
HttpContent contentDescr = new FormUrlEncodedContent(postData);
clientDescr.PutAsync("http://localhost:8758/api/upload", contentDescr).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
but this code calls second put method (without parameters). Why and how to call first put method correctly?

You have several options here:
You can either choose to pass the parameters in the query string, by just changing the URI to:
http://localhost:8758/api/upload?filename=test&description=100
or you can have Web API parse the form data for you by changing your action to look like this:
public void Put(FormDataCollection formData)
{
string fileName = formData.Get("fileName");
string description = formData.Get("description");
}
You can also choose to create a class that has a fileName and a description property and use that as your parameter and Web API should be able to bind it correctly for you.

Related

After sending using [RelayCommand] in maui the setter in ViewModel receives truncated string in Maui

After sending using [RelayCommand] in maui the setter in ViewModel receives truncated string in Maui.
Example orginal string: "https://twit.memberfulcontent.com/rss/9039?auth=m9FZurRandomAuthonumbers6yB"
The value of URL is good here:
[RelayCommand]
async Task Tap(string Url)
{
System.Diagnostics.Debug.WriteLine("Tap Sending: " + Url);
await Shell.Current.GoToAsync($"{nameof(ShowPage)}?Url={Url}");
}
when recieving here URL is truncated:
namespace NerdNewsNavigator2.ViewModel;
[QueryProperty("Url", "Url")]
public partial class ShowViewModel : ObservableObject
{
#region Properties
readonly TwitService _twitService;
public ObservableCollection<Show> Shows { get; set; } = new();
public string Url
{
set
{ // value is truncated string from Tap above.
System.Diagnostics.Debug.WriteLine("ShowViewModel Recieving url: " + value);
_ = GetShows(value);
OnPropertyChanged(nameof(Shows));
}
}
// Code shortened for brevity
Example of passed string:
"https://twit.memberfulcontent.com/rss/9039"
It gets truncated at ?Auth
Any suggestions on what I may be doing wrong? Or suggestion on a better way to do this? It works fine for string that do not have a ? mark in them. One hundred percent working except on this specific type of string.
I was expecting the string not to be truncated.
Correct way to pass data between pages in MAUI:
When you navigate:
Dictionary<string, object> query = new()
{
{ nameof(MyModel), myModel }
};
await Shell.Current.GoToAsync($"{nameof(MyPage)}", query);
When you are navigated:
public void ApplyQueryAttributes(IDictionary < string , object > query)
{
myModel = query[nameof(MyModel)] as MyModel;
}
(This solves more than the problem with sanitization)

Testing a Multipart file upload Azure Function

So I have written a simple Azure Function (AF) that accepts (via Http Post method) an IFormCollection, loops through the file collection, pushes each file into an Azure Blob storage container and returns the url to each file.
The function itself works perfectly when I do a single file or multiple file post through Postman using the 'multipart/form-data' header. However when I try to post a file through an xUnit test, I get the following error:
System.IO.InvalidDataException : Multipart body length limit 16384 exceeded.
I have searched high and low for a solution, tried different things, namely;
Replicating the request object to be as close as possible to Postmans request.
Playing around with the 'boundary' in the header.
Setting 'RequestFormLimits' on the function.
None of these have helped so far.
The details are the project are as follows:
Azure Function v3: targeting .netcoreapp3.1
Startup.cs
public class Startup : FunctionsStartup
{
public IConfiguration Configuration { get; private set; }
public override void Configure(IFunctionsHostBuilder builder)
{
var x = builder;
InitializeConfiguration(builder);
builder.Services.AddSingleton(Configuration.Get<UploadImagesAppSettings>());
builder.Services.AddLogging();
builder.Services.AddSingleton<IBlobService,BlobService>();
}
private void InitializeConfiguration(IFunctionsHostBuilder builder)
{
var executionContextOptions = builder
.Services
.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>()
.Value;
Configuration = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", optional: true)
.AddEnvironmentVariables()
.Build();
}
}
UploadImages.cs
public class UploadImages
{
private readonly IBlobService BlobService;
public UploadImages(IBlobService blobService)
{
BlobService = blobService;
}
[FunctionName("UploadImages")]
[RequestFormLimits(ValueLengthLimit = int.MaxValue,
MultipartBodyLengthLimit = 60000000, ValueCountLimit = 10)]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "images")] HttpRequest req)
{
List<Uri> returnUris = new List<Uri>();
if (req.ContentLength == 0)
{
string badResponseMessage = $"Request has no content";
return new BadRequestObjectResult(badResponseMessage);
}
if (req.ContentType.Contains("multipart/form-data") && req.Form.Files.Count > 0)
{
foreach (var file in req.Form.Files)
{
if (!file.IsValidImage())
{
string badResponseMessage = $"{file.FileName} is not a valid/accepted Image file";
return new BadRequestObjectResult(badResponseMessage);
}
var uri = await BlobService.CreateBlobAsync(file);
if (uri == null)
{
return new ObjectResult($"Could not blob the file {file.FileName}.");
}
returnUris.Add(uri);
}
}
if (!returnUris.Any())
{
return new NoContentResult();
}
return new OkObjectResult(returnUris);
}
}
Exception Thrown:
The below exception is thrown at the second if statement above, when it tries to process req.Form.Files.Count > 0, i.e.
if (req.ContentType.Contains("multipart/form-data") && req.Form.Files.Count > 0) {}
Message:
System.IO.InvalidDataException : Multipart body length limit 16384 exceeded.
Stack Trace:
MultipartReaderStream.UpdatePosition(Int32 read)
MultipartReaderStream.ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
StreamHelperExtensions.DrainAsync(Stream stream, ArrayPool`1 bytePool, Nullable`1 limit, CancellationToken cancellationToken)
MultipartReader.ReadNextSectionAsync(CancellationToken cancellationToken)
FormFeature.InnerReadFormAsync(CancellationToken cancellationToken)
FormFeature.ReadForm()
DefaultHttpRequest.get_Form()
UploadImages.Run(HttpRequest req) line 42
UploadImagesTests.HttpTrigger_ShouldReturnListOfUploadedUris(String fileNames)
xUnit Test Project: targeting .netcoreapp3.1
Over to the xUnit Test project, basically I am trying to write an integration test. The project references the AF project and has the following classes:
TestHost.cs
public class TestHost
{
public TestHost()
{
var startup = new TestStartup();
var host = new HostBuilder()
.ConfigureWebJobs(startup.Configure)
.ConfigureServices(ReplaceTestOverrides)
.Build();
ServiceProvider = host.Services;
}
public IServiceProvider ServiceProvider { get; }
private void ReplaceTestOverrides(IServiceCollection services)
{
// services.Replace(new ServiceDescriptor(typeof(ServiceToReplace), testImplementation));
}
private class TestStartup : Startup
{
public override void Configure(IFunctionsHostBuilder builder)
{
SetExecutionContextOptions(builder);
base.Configure(builder);
}
private static void SetExecutionContextOptions(IFunctionsHostBuilder builder)
{
builder.Services.Configure<ExecutionContextOptions>(o => o.AppDirectory = Directory.GetCurrentDirectory());
}
}
}
TestCollection.cs
[CollectionDefinition(Name)]
public class TestCollection : ICollectionFixture<TestHost>
{
public const string Name = nameof(TestCollection);
}
HttpRequestFactory.cs: To create Http Post Request
public static class HttpRequestFactory
{
public static DefaultHttpRequest Create(string method, string contentType, Stream body)
{
var request = new DefaultHttpRequest(new DefaultHttpContext());
var contentTypeWithBoundary = new MediaTypeHeaderValue(contentType)
{
Boundary = $"----------------------------{DateTime.Now.Ticks.ToString("x")}"
};
var boundary = MultipartRequestHelper.GetBoundary(
contentTypeWithBoundary, (int)body.Length);
request.Method = method;
request.Headers.Add("Cache-Control", "no-cache");
request.Headers.Add("Content-Type", contentType);
request.ContentType = $"{contentType}; boundary={boundary}";
request.ContentLength = body.Length;
request.Body = body;
return request;
}
private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
if (string.IsNullOrWhiteSpace(boundary.Value))
{
throw new InvalidDataException("Missing content-type boundary.");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded.");
}
return boundary.Value;
}
}
The MultipartRequestHelper.cs class is available here
And Finally the Test class:
[Collection(TestCollection.Name)]
public class UploadImagesTests
{
readonly UploadImages UploadImagesFunction;
public UploadImagesTests(TestHost testHost)
{
UploadImagesFunction = new UploadImages(testHost.ServiceProvider.GetRequiredService<IBlobService>());
}
[Theory]
[InlineData("testfile2.jpg")]
public async void HttpTrigger_ShouldReturnListOfUploadedUris(string fileNames)
{
var formFile = GetFormFile(fileNames);
var fileStream = formFile.OpenReadStream();
var request = HttpRequestFactory.Create("POST", "multipart/form-data", fileStream);
var response = (OkObjectResult)await UploadImagesFunction.Run(request);
//fileStream.Close();
Assert.True(response.StatusCode == StatusCodes.Status200OK);
}
private static IFormFile GetFormFile(string fileName)
{
string fileExtension = fileName.Substring(fileName.IndexOf('.') + 1);
string fileNameandPath = GetFilePathWithName(fileName);
IFormFile formFile;
var stream = File.OpenRead(fileNameandPath);
switch (fileExtension)
{
case "jpg":
formFile = new FormFile(stream, 0, stream.Length,
fileName.Substring(0, fileName.IndexOf('.')),
fileName)
{
Headers = new HeaderDictionary(),
ContentType = "image/jpeg"
};
break;
case "png":
formFile = new FormFile(stream, 0, stream.Length,
fileName.Substring(0, fileName.IndexOf('.')),
fileName)
{
Headers = new HeaderDictionary(),
ContentType = "image/png"
};
break;
case "pdf":
formFile = new FormFile(stream, 0, stream.Length,
fileName.Substring(0, fileName.IndexOf('.')),
fileName)
{
Headers = new HeaderDictionary(),
ContentType = "application/pdf"
};
break;
default:
formFile = null;
break;
}
return formFile;
}
private static string GetFilePathWithName(string filename)
{
var outputFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
return $"{outputFolder.Substring(0, outputFolder.IndexOf("bin"))}testfiles\\{filename}";
}
}
The test seems to be hitting the function and req.ContentLength does have a value. Considering this, could it have something to do with the way the File Streams are being managed? Perhaps not the right way?
Any inputs on this would be greatly appreciated.
Thanks
UPDATE 1
As per this post, I have also tried setting the ValueLengthLimit and MultipartBodyLengthLimit in the Startup of the Azure Function and/or the Test Project as opposed to attributes on the Azure Function. The exception then changed to:
"The inner stream position has changed unexpectedly"
Following this, I then set the fileStream position in the test project to SeekOrigin.Begin. I started getting the same error:
"Multipart body length limit 16384 exceeded."
It took me a 50km bike ride and a good nights sleep but I finally figured this one out :-).
The Azure function (AF) accepts an HttpRequest object as a parameter with the name of 'req' i.e.
public async Task Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "images")] HttpRequest req)
The hierarchy of the files object in the HttpRequest object (along with the parameter names) is as follows:
HttpRequest -> req
FormCollection -> Form
FormFileCollection -> Files
This is what the AF accepts and one would access the files collection by using req.Form.Files
In my test case, instead of posting a FormCollection object, I was trying to post a Stream of a file to the Azure Function.
var formFile = GetFormFile(fileNames);
var fileStream = formFile.OpenReadStream();
var request = HttpRequestFactory.Create("POST", "multipart/form-data", fileStream);
As a result of this, req.Form had a Stream value that it could not interpret and the req.Form.Files was raising an exception.
In order to rectify this, I had to do the following:
Revert all changes made as part of UPDATE 1. This means that I removed the 'RequestFormLimits' settings from the Startup file and left them as attributes on the functions Run method.
Instantiate a FormFileCollection object and add the IFormFile to it
Instantiate a FormCollection object using this FormFileCollection as a parameter.
Add the FormCollection to the request object.
To achieve the above, I had to make the following changes in code.
Change 'Create' method in the HttpRequestFactory
public static DefaultHttpRequest Create(string method, string contentType, FormCollection formCollection)
{
var request = new DefaultHttpRequest(new DefaultHttpContext());
var boundary = $"----------------------------{DateTime.Now.Ticks.ToString("x")}";
request.Method = method;
request.Headers.Add("Cache-Control", "no-cache");
request.Headers.Add("Content-Type", contentType);
request.ContentType = $"{contentType}; boundary={boundary}";
request.Form = formCollection;
return request;
}
Add a private static GetFormFiles() method
I wrote an additional GetFormFiles() method that calls the existing GetFormFile() method, instantiate a FormFileCollection object and add the IFormFile to it. This method in turn returns a FormFileCollection.
private static FormFileCollection GetFormFiles(string fileNames)
{
var formFileCollection = new FormFileCollection();
foreach (var file in fileNames.Split(','))
{
formFileCollection.Add(GetFormFile(file));
}
return formFileCollection;
}
Change the Testmethod
The test method calls the GetFormFiles() to get a FormFileCollection then
instantiates a FormCollection object using this FormFileCollection as a parameter and then passes the FormCollection object as a parameter to the HttpRequest object instead of passing a Stream.
[Theory]
[InlineData("testfile2.jpg")]
public async void HttpTrigger_ShouldReturnListOfUploadedUris(string fileNames)
{
var formFiles = GetFormFiles(fileNames);
var formCollection = new FormCollection(null, formFiles);
var request = HttpRequestFactory.Create("POST", "multipart/form-data", formCollection);
var response = (OkObjectResult) await UploadImagesFunction.Run(request);
Assert.True(response.StatusCode == StatusCodes.Status200OK);
}
So in the end the issue was not really with the 'RequestFormLimits' but rather with the type of data I was submitting in the POST message.
I hope this answer provides a different perspective to someone that comes across the same error message.
Cheers.

How to send user defined details to activity in Bot Framework App Or How to access BotData in my dialog class

I have two questions
1) How to send custom values to Activity in Bot Framework?
I have below code in post method
UserDetails usr = new UserDetails();
usr.LoginID = l.LoginID
userData.SetProperty<string>("LoginID", usr.LoginID);
sc.BotState.SetUserData(activity.ChannelId, activity.From.Id, userData);
Now I want to access this property in my Dialog/FormBuilder class, How to achieve this?
Below is my formbuilder class
[Serializable]
public class FlightBooking
{
public static IForm<FlightBooking> BuildForm()
{
return new FormBuilder<FlightBooking>().Message("Tell me flight details!")
.Field(nameof(title))
....
....
}
}
2) How to access BotData user defined properties in FormBuilder/Dialog class?
As in above code, you can see that I have set EmailId property, how to access that property value in formbuilder class?
1) I don't understand what you're doing with BotState. The question you asked is "How to send custom values to Activity...", but you are actually saving values into UserData. Also, the code you've provided does not seem complete.
You can save UserData like this:
var stateClient = activity.GetStateClient();
BotData userData = stateClient.BotState.GetUserData(activity.ChannelId, activity.From.Id);
userData.SetProperty<string>("LoginID", l.LoginID);
stateClient.BotState.SetUserData(activity.ChannelId, activity.From.Id, userData);
2) You can send the activity.ChannelId and activity.From.Id to the BuildForm method:
await Conversation.SendAsync(activity, ()=> FormDialog.FromForm(() => FlightBooking.BuildForm(activity.ChannelId, activity.From.Id)));
and create the StateClient, and access the UserData:
public class FlightBooking
{
public string title { get; set; }
public static IForm<FlightBooking> BuildForm(string channelId, string userId)
{
string appId = ConfigurationManager.AppSettings["MicrosoftAppId"];
string password = ConfigurationManager.AppSettings["MicrosoftAppPassword"];
StateClient stateClient = new StateClient(new MicrosoftAppCredentials(appId, password));
BotData userData = stateClient.BotState.GetUserData(channelId, userId);
var loginId = userData.GetProperty<string>("LoginID");
//do something with loginId?
return new FormBuilder<FlightBooking>().Message("Tell me flight details!")
.Field(nameof(title))
.Build();
}
}
But, you could also just send the entire activity object to the BuildForm method, and not bother with UserData at all.

How do I display a PDF using PdfSharp in ASP.Net MVC?

We're making an ASP.Net MVC app that needs to be able to generate a PDF and display it to the screen or save it somewhere easy for the user to access. We're using PdfSharp to generate the document. Once it's finished, how do we let the user save the document or open it up in a reader? I'm especially confused because the PDF is generated server-side but we want it to show up client-side.
Here is the MVC controller to create the report that we have written so far:
public class ReportController : ApiController
{
private static readonly string filename = "report.pdf";
[HttpGet]
public void GenerateReport()
{
ReportPdfInput input = new ReportPdfInput()
{
//Empty for now
};
var manager = new ReportPdfManagerFactory().GetReportPdfManager();
var documentRenderer = manager.GenerateReport(input);
documentRenderer.PdfDocument.Save(filename); //Returns a PdfDocumentRenderer
Process.Start(filename);
}
}
When this runs, I get an UnauthorizedAccessException at documentRenderer.PdfDocument.Save(filename); that says, Access to the path 'C:\Program Files (x86)\Common Files\Microsoft Shared\DevServer\10.0\report.pdf' is denied. I'm also not sure what will happen when the line Process.Start(filename); is executed.
This is the code in manager.GenerateReport(input):
public class ReportPdfManager : IReportPdfManager
{
public PdfDocumentRenderer GenerateReport(ReportPdfInput input)
{
var document = CreateDocument(input);
var renderer = new PdfDocumentRenderer(true, PdfSharp.Pdf.PdfFontEmbedding.Always);
renderer.Document = document;
renderer.RenderDocument();
return renderer;
}
private Document CreateDocument(ReportPdfInput input)
{
//Put content into the document
}
}
Using Yarx's suggestion and PDFsharp Team's tutorial, this is the code we ended up with:
Controller:
[HttpGet]
public ActionResult GenerateReport(ReportPdfInput input)
{
using (MemoryStream stream = new MemoryStream())
{
var manager = new ReportPdfManagerFactory().GetReportPdfManager();
var document = manager.GenerateReport(input);
document.Save(stream, false);
return File(stream.ToArray(), "application/pdf");
}
}
ReportPdfManager:
public PdfDocument GenerateReport(ReportPdfInput input)
{
var document = CreateDocument(input);
var renderer = new PdfDocumentRenderer(true,
PdfSharp.Pdf.PdfFontEmbedding.Always);
renderer.Document = document;
renderer.RenderDocument();
return renderer.PdfDocument;
}
private Document CreateDocument(ReportPdfInput input)
{
//Creates a Document and puts content into it
}
I'm not familar with PDF sharp but for MVC is mostly done via built in functionality. You need to get your pdf document represented as an array of bytes. Then you'd simply use MVC's File method to return it to the browser and let it handle the download. Are there any methods on their class to do that?
public class PdfDocumentController : Controller
{
public ActionResult GenerateReport(ReportPdfInput input)
{
//Get document as byte[]
byte[] documentData;
return File(documentData, "application/pdf");
}
}

Render an MVC3 action to a string from a WCF REST service method

I have a WCF REST service that takes some parameters and sends an email. The template for the email is an MVC3 action. Essentially I want to render that action to a string.
If it were an ASP.NET WebForm, I could simply use Server.Execute(path, stringWriter, false). However when I plug in the path to my action, I get Error executing child request.
I have full access to HttpContext from my service (AspNetCompatibilityRequirementsMode.Allowed).
I know there are other answers out there for rendering actions to strings from within the context of a controller. How do I do this when I'm outside that world, but still on the same server (and, for that matter, in the same app)?
I cobbled together an answer based on several different google searches. It works, but I'm not 100% sure it's as lean as it could be. I'll paste the code for others to try.
string GetEmailText(TemplateParameters parameters) {
// Get the HttpContext
HttpContextBase httpContextBase =
new HttpContextWrapper(HttpContext.Current);
// Build the route data
var routeData = new RouteData();
routeData.Values.Add("controller", "EmailTemplate");
routeData.Values.Add("action", "Create");
// Create the controller context
var controllerContext = new ControllerContext(
new RequestContext(httpContextBase, routeData),
new EmailTemplateController());
var body = ((EmailTemplateController)controllerContext.Controller)
.Create(parameters).Capture(controllerContext);
return body;
}
// Using code from here:
// http://blog.approache.com/2010/11/render-any-aspnet-mvc-actionresult-to.html
public class ResponseCapture : IDisposable
{
private readonly HttpResponseBase response;
private readonly TextWriter originalWriter;
private StringWriter localWriter;
public ResponseCapture(HttpResponseBase response)
{
this.response = response;
originalWriter = response.Output;
localWriter = new StringWriter();
response.Output = localWriter;
}
public override string ToString()
{
localWriter.Flush();
return localWriter.ToString();
}
public void Dispose()
{
if (localWriter != null)
{
localWriter.Dispose();
localWriter = null;
response.Output = originalWriter;
}
}
}
public static class ActionResultExtensions
{
public static string Capture(this ActionResult result, ControllerContext controllerContext)
{
using (var it = new ResponseCapture(controllerContext.RequestContext.HttpContext.Response))
{
result.ExecuteResult(controllerContext);
return it.ToString();
}
}
}

Resources