I have a Blazor Server app where the users can use filters to see a list of movies. For example, they can specify the year, the country and etc. and movies that match the criteria will be displayed on the page (a list of Movie objects).
I want to give users the ability to save the displayed list as a text file. I know how to make an HTTP Get request to another Razor page or an MVC or API controller to return a predefined file but am struggling with passing any arguments to the endpoint.
Initially I thought of using HttpClient.PostAsJsonAsync to send a POST request to an API controller, passing the list as the request body, which works fine in the sense that if I put a breakpoint in the controller method it is hit but nothing is returned to the user.
If I use an <a> element or a button onClick method with NavigationManager.NavigateTo it works fine for a predetermined file HTTP Get request but how can I serve the users with a file that consists of the list of movies/objects they are seeing on the browser?
I.e. How can I either pass arguments with NavigationManager.NavigateTo or using an <a> element or any other way in order to send data to the endpoint/server which it then can use to generate a file and return that file to the user for saving?
Mock code to give an idea of what I'd like. This is just a design/idea reference, doesn't have to be WebApi, MVC or whatever. I just need something that would work with Blazor:
[HttpPost]
File WebApiMethodThatDoesTheWork(CustomObject data)
{
StringBuilder stringBuilder = new();
foreach (var item in data.Items)
{
stringBuilder.Append(item);
}
File.WriteAllText("test.txt", stringBuilder.ToString());
return File("test.txt");
}
The only two requirements I have about how the file will be downloaded are: it should be user-friendly, meaning the usual "Do you want to open or save this file?" dialog box would be perfect AND it shouldn't use the GET request to a Razor page to retrieve the file UNLESS a single Razor page can be used to download many different file types. I.e. if a user wants to download a list of movies, while another a list of songs. With my current knowledge I think for such a case I'd have to have 2 different Razor pages, which is suboptimal.
I don't have a complete packaged up answer because mine is old and Blazor works differently now, but the underlying principal is the same.
Step 1 - JS Interop call to URL.createObjectURL(new Blob([{your data}]))
Step 2 - Render an anchor tag with the resulting object url as the href and a download attribute.
<a id="download" href=#BlobUrl download=#FileName>Download File</a>
Step 3 - Optionally JS Interop call to click the rendered anchor tag so the user doesn't have to.
Whether the user sees a Save As.. dialog or not depends on their browser/operating system - you can't control that (There is a downloads api that has saveas functionality, but it's not fully supported)
You could try re-filtering the list using that file and upon start-up you can check if they have that file... there's lots of options you can chose from
Related
I have a form which is validated server side and if the form has invalid items, the model is returned to the View and JavaScript then highlights fields which are invalid. This is a multi-part form which contains an input type file for a file upload. I am using Visual Studio 2017, ASP.Net Core 2.1 and ASP.Net MVC design pattern.
Problem: User submits the form and has a file attached but the form is invalid due to one or many input fields. When passing the Model back to the View with validation errors, the file is no longer attached as you can't populate an input type file from the server (at least not that I know of, think this is a security feature).
Desired Behavior: The user doesn't have to re-select the file in the input for upload a second time.
Current Solution: I serialize and save the uploaded file in Session on the server and then set a property on the Model which tells the View that a file is saved in session and remove the input type file for upload, then on resubmit, if the form is valid, deserialize the file out of Session, do what I need to do with the file, and remove it from Session.
Question: Is there a better way of handling this? If so, please offer example documentation or advice.
As you guessed, for security reasons, no major browsers gives you access to the actual path to the file selected by the user. Your server code will not receive the real path, and even JavaScript is not allowed access to it. Instead, the browser generates a fake path, and the only real value in it is the file name.
For that reason, the only way to keep the file in your current design (i.e. standard form) after the form is submitted is the way you did it as you described it in your question. Obviously, this is not a good way especially if the file is too big, then each round trip to the server will take a long time.
To solve it differently, you'll need to change the design. You need to make the browser retain the real path value. The only way to do this is by not submitting the form. So you need to submit the form values without submitting the form. The only way to do this I can think of is by submitting the form using Ajax. This means that the result of the server-side validation will have to come back as an Ajax response (usually JSON, but can be serialized in other ways if you like) and then JavaScript will take that response and display the errors on the form as required. In this way, the file is still re-submitted every time to the server, but at least the server doesn't need to send it back to the client like the way described in the question. That saves half of the bandwidth, but will require more programming. It is still not recommended if the file can be big. In that case, it is probably better to let the user reselect the file again rather than having to wait a long time for the file to be uploaded again.
To add the Ajax helper to .Net Core, you can simply download the JavaScript file from the project GitHub and link to it in the header of your HTML. However, I prefer to only link to it on the pages where I need it, because most pages don't need it. What I do is, first create a partial view for it:
<environment names="Development">
<script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.js"></script>
</environment>
<environment exclude="Development">
<script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.min.js"></script>
</environment>
You can use CDN if you like. I cannot find a CDN other than cdn.jsdelivr.net, and I cannot find a good fallback test so I left asp-fallback-test empty:
<environment names="Development">
<script src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.js"></script>
</environment>
<environment exclude="Development">
<script src="https://cdn.jsdelivr.net/npm/jquery-ajax-unobtrusive#3.2.6/dist/jquery.unobtrusive-ajax.min.js"
asp-fallback-src="~/lib/jquery-ajax-unobtrusive/dist/jquery.unobtrusive-ajax.min.js"
asp-fallback-test=""
crossorigin="anonymous"
integrity="sha256-PAC000yuHt78nszJ2RO0OiDMu/uLzPLRlYTk8J3AO10="></script>
</environment>
Either way, call this file anything you like, let's say _AjaxScriptsPartial.cshtml. Now in the view where you need it, add it like this:
#section Scripts {
#{ await Html.RenderPartialAsync("_AjaxScriptsPartial"); }
}
Here is a good explanation on how to use it: Using Unobtrusive Ajax In Razor Pages.
Alternatively, you can keep your current design, but instead of serializing the file and send it back to the browser, save it on the server and send the URL to the browser. Now, in the JavaScript, replace the file input field with a simple message like "File uploaded successfully" or any other way to indicate to the user that the file doesn't need to be re-selected. When the form is re-submitted and all server validation succeed, get the physical file on the server. Your code will have to know whether to upload the file or get it from the server (by checking if the file input field has a file). It also needs to know where to get it on the server. This way will work fine for all file sizes, because the files is only uploaded once, but it requires some work.
I'm not sure of the validation you are doing, but can it be moved to the front-end? Typically validation in the front-end is more for user experience. (Eg. Incorrect Formats, fields left blank, incorrect data type in field). If that's the case I'd move it to the front-end, if not you can leave it in the back-end.
You could validate the fields in the form using AJAX when they leave focus. This can guarantee that when they submit the form, the fields in the form will always be correct. I find a healthy mix of front-end and server side validation works best and not have everything in the back-end.
The crude example below sends off an ajax request to the controller to validate the input field when the onblur method fires. It will set the border to either green or red if it passed validation and display a message if there is one to display. Just make sure your model matches up with what you would be sending in your request:
JS:
<script>
function validate() {
var input = { input: $('#one').val() };
$.ajax({
url: 'ValidateTextBox/',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(input),
success: function (data) {
if (JSON.parse(data.Result)) {
$('#one').css('border', '5px solid green');
} else {
$('#one').css('border', '5px solid red');
}
$('#result').text(data.Message);
}
})
}
</script>
<input id="one" type="text" onblur="validate()"/>
<label id="result"></label>
Controller.cs
[HttpPost]
public ActionResult ValidateTextBox(Dummy data)
{
int result = 0;
if (!int.TryParse(data.input, out result)) {
return Json(new { Result = false, Message = "Please enter in numbers" });
}
return Json(new { Result = true, Message = "Success" });
}
Model.cs
public class Dummy
{
public string input { get; set; }
}
Please see following image.
Umbraco7 screen
I am using Umbraco 7. You can see in the image that I have 'General Messages' tab.
I have saved all error messages in that and i need to access these error messages from the code, can I do that in Csharp ?
I'm going to assume that you have a template assigned to the sign up page, and that you want to get at the messages on that View.
That being the case, you can use either:
#Umbraco.Field("yourPropertyAliasHere")
or
#Model.Content.GetPropertyValue("yourPropertyAliasHere")
The main difference is that Umbraco.Field has a bunch of useful additional parameters for things like recursive lookup.
If you want get at the properties from within some random C# in your Umbraco site that isn't related t the actual signup page, assuming you have an Umbraco Helper, you can do the following:
var page = Umbraco.TypedContent(1234); //replace '1234' with the id of your signup page!
var message = page.GetPropertyValue<string>("yourPropertyAliasHere");
I'm not sure of the best way to accomplish my goal. Looking for insight. I'm familiar with WebAPI services consumed through WPF and Silverlight but this is my first run at ASP and MVC.
I am building a site to verify contents of a shipment against an electronic manifest (EDI 856). I have a page that displays the shipping data and I need the users to scan each item barcode in the container. I would then like to pass that barcode to a service, verify the item belongs in that shipment and then update the page to show as much.
My plan was to have a single text box into which the user could scan/type the barcode and then submit that data to a WebAPI service which would verify the information and then probably use SignalR to send a message back to the page and update a grid with the item data.
If this is a decent way to go, I'm just not quite sure how to use ajax to call the WebAPI endpoint and provide the data I need.
I would advise against using SignalR in this situtation. What you need, judging from your description, is the most basic use case of submitting an ajax request and receiving a response.
You are not designing a system where you need the server to initiate communication with the browser or anything like that, where sockets (and SignalR as an abstraction over sockets with fallbacks to less suitable protocols) is a huge overkill.
Don't worry, your use case is rather simple.
It's a little out of scope to describe how to setup a WebApi project, how to configure routing, action names, etc. Simple google searches will surely provide ample quality tutorials on getting started.
I'll just try to explain what the general idea is, with some code samples, to get you thinking in the right direction.
You need to create an ApiController.
The simplest version of that Controller will probably look something like this:
public class ShipmentVerificationController : ApiController
{
//this is the response object you will be sending back to the client website
public class VerificationResult
{
public bool Valid;
}
public VerificationResult GetIsItemValid(string BarCode)
{
bool itemIsValid;
// Implement checks against the BarCode string here
itemIsValid = true;
return new VerificationResult { Valid = itemIsValid };
}
}
Note that the inner class represents the response you will be sending back. It should be properly filled out with additional info if needed and probably put into a separate .cs file in the "Models" folder or where ever you see fit.
I have declared it inside the controller for demonstration purposes only
Once you have a WebApi service deployed, it's really easy to send it data from your website and receive the feedback.
To simplify Ajax requests, jQuery is often used.
Once the user inputs the barcode into a textbox, you can hook up an event to check for return key being pressed (most barcode scanners send the return key command after they input the barcode data) and then write something along the lines of:
var barcode = $("#input-field").val();
$.getJSON( "<url_to_your_webapi_service>/api/ShipmentVerification/GetIsItemValid/" + barcode, function( data ) {
if (data.Valid) {
// great, highlight the item as valid
}
else {
//better indicate an error with the scanned item
}
});
Please note that for simplicity I have not included any error handling, url parameter encoding, and most importantly, zero authorization.
Authorization is very important if you deploy the web service to the open web but still do not want anyone to be able to call it.
You will have to research these topics yourself, but I hope I have presented you the core concepts and logic behind a simple service such as this, so you have a base to start with.
If you come up with specific problems and questions post a new question.
I actually found a more simple way to do this. I nixed the idea of using a WebAPI endpoint and just went with a normal controller. I used ajax to prevent the page from refreshing with the new view, since that view is actually just json data with my return values in it.
I was just assigned to implement one functionality in project that uses Umbraco. My job is to basically generate specific XML and return it to user. However i cannot get it to work, because when i create new controller (i've tried creating
Controller, RenderMvcController and SurfaceController
) and method in it (also if i just create new method in existing controller), i get error 404 after typing url into browser. Example: I create TestController and method Index in it. I've tried combinations where TestController was derived from RenderMvcController or SurfaceController or just Controller. After compiling, etc. when i run
http://my_address/Test
or
http://my_address/Test/Index
i get 404 error from umbraco. I looked at another pages in umbraco that were already in project and they all are also configured somehow in umbraco web panel:
http://my_address/umbraco
I aslo tried adding new methods to existings controllers, but no luck (again 404 errors). I've never worked with umbraco and i don't know how to configure it. I just want to know if there is any way to create method which will be accessible at:
http://my_address/MyMethod
or
http://my_address/MyController/MyMethod
and would return just exactly what i will program it to (without any Views, Partial Views, etc. - i can set Headers and ContentType manually and my content is pure text) in an existing Umbraco project without having to deal with umbraco admin panel?
Thanks for any help :)
//Edit
My mind is officially blown... My response is culture dependent (i mean i pull different data from db depending on country), but it's not as simple as
CurrentCulture.CultureInfo
Umbraco is configured to return different culture based on domain extension (Germany for .de, Great Britain for .co.uk, and Dennmark for .dk - it's just a manual configuration in umbraco admin panel assigning different culture info and views to different hostnames). Regular controllers get this modified culture from
RenderModel.CurrentCulture
passed as argument to controller's method. Is there a way to create umbraco controller/method/anthing that will not have layout/model assigned to it (so i can display pure XML data i receive from external service) and still have access to umbraco's RenderModel's culture? What i am trying to create is if user types url:
http://my_address.de/myController/myMethod
my controller will get current culture, call external service passing culture as parameter and display received data without wrapping it in any views. Example:
public class myController : SomeBaseUmbracoControllerOrsomething
{
public string/XmlDocument/ActionResult myMethod(RenderModel model)
{
int countryId = myFunctionToTranslateCultureToCountryId(model.CurrentCulture);
return MethodThatCallsExternalServiceAndReturnsXml(countryId);
}
}
Sorry for confusion, but i've learned about this whole mess with countries just now...
You don't want to use
controller, because this is not picked up by umbraco routing process
you don't want to use RenderMvcController, because this is overkill
you don't want to use Surfacecontroller because you are not using a Child action or form.
What you need is a UmbracoApiController (http://our.umbraco.org/documentation/Reference/WebApi/) or is your umbraco version is PRE 6.1 then use /Base extention (http://our.umbraco.org/documentation/Reference/Api/Base/Index)
Or if you really want to skip ALL umbraco magic for a certain route, add the path to the web.config/AppSettings/umbracoReservedUrls.
Is it possible to vary the output cache in MVC based on certain values in the session? I've read a lot about using the varybycustom functionality and overriding GetVaryByCustomString in Global.asax but the session is not available at this point.
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom == "somekey")
//Want to check the session here (but it isn't available).
return base.GetVaryByCustomString(context, custom);
}
I understand this is because the Session isn't created until later in the request pipeline.
My concern is that without varying the cache based on the user's session, the page (which changes based on what the user has in the session, has additional HTML specific to that user etc) will get cached (as the URL is the same) and served by our load balancer, proxy servers etc. and then served to other requests with other people's session information on the page!
The reason the URL is the same is that the user comes in as a 'guest', enters some information (POST), this is validated and stored in the session and then they are re-directed back to the same page (which should now be specific to the user based on the session data).
The page itself should be cached normally because if a 'guest' visits the same URL, it should serve the same 'standard' page every time.
Is is possible to vary the caching in this way?
If you want to personalize the cache output per user, it is better you set the Location to OutputCacheLocation.Client as below. More information here
[OutputCache(Duration=3600, VaryByParam="none", Location=OutputCacheLocation.Client, NoStore=true)]
public string GetName()
{
return "Hi " + User.Identity.Name;
}
Would a Output Cache ActionFilter help at all?
Or perhaps you could refactor your view in to a layout page plus partial views for anonymous and authenticated sections, then utilize Partial Caching.
You should look into "Donut Caching", but this isn`t supported by ASP.NET MVC 3, at least not out of the box. Fortunately somebody already solved this problem for you see MvcDonutCaching
I read that ASP.NET MVC 4 will include "Donut Hole Caching" out of the box, but i cant tell if it's in the current RC or not.