Problem
I'm trying to create an ASP.NET Core (3.1) web application that accepts file uploads and then breaks it into chunks to send to Sharepoint via MS Graph API. There are a few other posts here that address the similar questions but they assume a certain level of .NET knowledge that I don't have just yet. So I'm hoping someone can help me cobble something together.
Configure Web server & app to Accept Large Files
I have done the following to allow IIS Express to upload up to 2GB files:
a) created a web.config file with the following code:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="Home/UploadFile">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<security>
<requestFiltering>
<!--unit is bytes => 2GB-->
<requestLimits maxAllowedContentLength="2147483647" />
</requestFiltering>
</security>
</system.webServer>
</location>
</configuration>
B) I have the following in my Startup.cs Configuration section:
//Add support for uploading large files TODO: DO I NEED THIS?????
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue; // Limit on individual form values
x.MultipartBodyLengthLimit = int.MaxValue; // Limit on form body size
x.MultipartHeadersLengthLimit = int.MaxValue; // Limit on form header size
});
services.Configure<IISServerOptions>(options =>
{
options.MaxRequestBodySize = int.MaxValue; //2GB
});
Here's what my form looks like that allows the user to pick the file and submit:
#{
ViewData["Title"] = "Messages";
}
<h1>#ViewData["Title"]</h1>
<p></p>
<form id="uploadForm" action="UploadFile" method="post" enctype="multipart/form-data">
<dl>
<dt>
<label for="file">File</label>
</dt>
<dd>
<input id="file" type="file" name="file" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output form="uploadForm" name="result"></output>
</div>
</form>
Here's what the controller looks like:
[HttpPost]
[RequestSizeLimit(2147483647)] //unit is bytes => 2GB
[RequestFormLimits(MultipartBodyLengthLimit = 2147483647)]
public async void UploadFile()
{
User currentUser = null;
currentUser = await _graphServiceClient.Me.Request().GetAsync();
//nothing have to do with the file has been written yet.
}
When the user clicks on the file button and chooses a large file, I no longer get IIS 413 error messages. Great. The logic hits the right method in my controller.
But I have the following questions for this part of the code:
When the user picks the file ... what is actually happening under the hood? Has the file actually been stuffed into my form and is accessible from my controller?
Is it a stream?
how do i get to the file?
If ultimately, I need to send this file to Sharepoint using this type of an approach (the last example on chunking), it seems that the best approach is to save the file on my server somewhere... and then copy the sample code and try to chunk it out? The sample code seems to be referring to file paths and file sizes, I'm assuming I need to persist it to my web server somewhere first, and then take it from there.
if i do need to save it, can you point me in the right direction - maybe some sample code that shows me how to take the POSTed data in my form and save it?
ultimately, this will need to be refactored os that there is not GUI ... but it's just an API that accepts large files to upload somewhere. But I think i'll try to learn how to do it this way first... and then refactor to change my code to be API only.
Sorry for the noob questions. I have tried to do my research before posting here. But somethings are still a bit fuzzy.
EDIT 1
Per the suggestion in one of the posted answers, i've downloaded sample code that demonstrates how to bypass saving to a local file on the web server. It's based on this article
I have created a web.config file again -to avoid the 413 errors from IIS. I have also edited the list of allowed file extensions to support .pdf and .docx and .mp4.
When I try to run the sample project, and I choose the "Stream a file with AJAX to a controller endpoint" under the "Physical Storage Upload Examples" section, it dies here:
// This check assumes that there's a file
// present without form data. If form data
// is present, this method immediately fails
// and returns the model error.
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
if (!MultipartRequestHelper
.HasFileContentDisposition(contentDisposition))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 2).");
// Log error
return BadRequest(ModelState);
}
As is mentioned in the comments above the code, it's checking for form data and then when it finds it... it dies. So i've been playing around with the HTML page which looked like this:
<form id="uploadForm" action="Streaming/UploadPhysical" method="post"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;">
<dl>
<dt>
<label for="file">File</label>
</dt>
<dd>
<input id="file" type="file" name="file" />asdfasdf
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output form="uploadForm" name="result"></output>
</div>
</form>
And I've tried to remove the form like this:
<dl>
<dt>
<label for="file">File</label>
</dt>
<dd>
<input id="file" type="file" name="file" />
</dd>
</dl>
<input class="btn" type="button" asp-controller="Streaming" asp-action="UploadPhysical" value="Upload" />
<div style="margin-top:15px">
<output form="uploadForm" name="result"></output>
</div>
But the button doesn't do anything now when I click it.
Also, in case you're wondering / it helps, I manually copied in a file into the c:\files folder on my computer and when the sample app opens, it does list the file - proving it can read the folder.
I added read /write permissions so hopefully the web app can write to it when I get that far.
I've implemented a similar large file controller but using mongoDB GridFS.
In any case, streaming is the way to go for large files because it is fast and lightweight.
And yes, the best option is to save the files on your server storage before you send.
One suggestion is, add some validations to allow specefic extensions and restrict execution permissions.
Back to your questions:
The entire file is read into an IFormFile, which is a C# representation of the file used to process or save the file.
The resources (disk, memory) used by file uploads depend on the number and size of concurrent file uploads. If an app attempts to buffer too many uploads, the site crashes when it runs out of memory or disk space. If the size or frequency of file uploads is exhausting app resources, use streaming.
source 1
The CopyToAsync method enables you to perform resource-intensive I/O operations without blocking the main thread.
source 2
Here you have examples.
Example 1:
using System.IO;
using Microsoft.AspNetCore.Http;
//...
[HttpPost]
[Authorize]
[DisableRequestSizeLimit]
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
[Route("upload")]
public async Task<ActionResult> UploadFileAsync(IFormFile file)
{
if (file == null)
return Ok(new { success = false, message = "You have to attach a file" });
var fileName = file.FileName;
// var extension = Path.GetExtension(fileName);
// Add validations here...
var localPath = $"{Path.Combine(System.AppContext.BaseDirectory, "myCustomDir")}\\{fileName}";
// Create dir if not exists
Directory.CreateDirectory(Path.Combine(System.AppContext.BaseDirectory, "myCustomDir"));
using (var stream = new FileStream(localPath, FileMode.Create)){
await file.CopyToAsync(stream);
}
// db.SomeContext.Add(someData);
// await db.SaveChangesAsync();
return Ok(new { success = true, message = "All set", fileName});
}
Example 2 with GridFS:
[HttpPost]
[Authorize]
[DisableRequestSizeLimit]
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = int.MaxValue)]
[Route("upload")]
public async Task<ActionResult> UploadFileAsync(IFormFile file)
{
if (file == null)
return Ok(new { success = false, message = "You have to attach a file" });
var options = new GridFSUploadOptions
{
Metadata = new BsonDocument("contentType", file.ContentType)
};
using (var reader = new StreamReader(file.OpenReadStream()))
{
var stream = reader.BaseStream;
await mongo.GridFs.UploadFromStreamAsync(file.FileName, stream, options);
}
return Ok(new { success = true, message = "All set"});
}
You are on the right path, but as others have pointed out Microsoft have put up a well written document on file uploading which is a must read in your situation - https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0#upload-large-files-with-streaming.
As for your questions
do you need services.Configure<FormOptions>(x =>
No you don't! And you don't need services.Configure<IISServerOptions>(options => either, its read from the maxAllowedContentLength that you have configured in your web.config
When the user picks the file ... what is actually happening under the hood? Has the file actually been stuffed into my form and is accessible from my controller?, Is it a stream?
If you disable the form value model binding and use the MultipartReader the file is streamed and won't be cached into memory or disk, as you drain the stream, more data will be accepted from the client(the browser)
how do i get to the file?
Check the document above, there is a working sample for accessing the stream.
If ultimately, I need to send this file to Sharepoint using this type of an approach (the last example on chunking), it seems that the best approach is to save the file on my server somewhere... and then copy the sample code and try to chunk it out? The sample code seems to be referring to file paths and file sizes, I'm assuming I need to persist it to my web server somewhere first, and then take it from there.
Not necessarily, using the streaming approach you can copy the stream data directly.
I want my C# code to get the currently-selected option from an HTML5 dropdown list. Our situation is perhaps a bit different from many others, since this is not a client-server web app, but rather a desktop app which uses HTML for its UI in order to be more portable to Linux. To support this, we use an embedded Firefox browser with the Gecko engine.
I'm new to working with HTML using C#, and I can't seem to find the right API call for this.
The HTML looks like this:
...
<div class="select">
<select id="ddlMyOptions" name="ddlMyOptions">
<option></option>
<option>first choice</option>
<option>second choice</option>
</select>
</div>
...
And the C# code I'm using to try to access it is:
IHTMLElement selectElement = documentHTML.GetElementByID("ddlMyOptions");
string value = selectElement.Items[selectElement.SelectedIndex].Value;
string text = selectElement.Items[selectElement.SelectedIndex].Text;
This is based on examples I have seen from other posts, but those posts are using asp.net and in my case Items and SelectedIndex are not defined for selectElement.
Also, I have seen some posts about using Javascript, but I would prefer to keep this all in C#, if possible.
I looked at what it would take to have a callback method on the dropdown, so I could get the selected value whenever it changed and save it to an instance variable in my class, but I wasn't able to figure out how to specify the callback method, either.
Any help would be highly appreciated!
Since there were no answers to my question, I went ahead with a JavaScript solution:
function updateDDLSelection(elid) {
var el = document.getElementById(""ddlMyOptions"");
var val = el.options[el.selectedIndex].value;
el.setAttribute('value', val);
}
Then, I had to change the HTML from what was shown above to this:
...
<div class="select">
<select id="ddlMyOptions" name="ddlMyOptions" onchange=""updateDDLSelection(event)"">
<option></option>
<option>first choice</option>
<option>second choice</option>
</select>
</div>
...
And here's the revised C# code to get the value that was set by the JavaScript function:
IHTMLElement selectElement = documentHTML.GetElementByID("ddlMyOptions");
string value = selectElement.GetAttribute("value", 0).ToString();
Hope this is helpful to someone else who is in a similar situation...
I'd like to restrict the type of file that can be chosen from the native OS file chooser when the user clicks the Browse button in the <input type="file"> element in HTML. I have a feeling it's impossible, but I'd like to know if there is a solution. I'd like to keep solely to HTML and JavaScript; no Flash please.
Strictly speaking, the answer is no. A developer cannot prevent a user from uploading files of any type or extension using front-end validation (HTML/JavaScript).But still, the accept attribute of <input type = "file"> can help to provide a filter in the file select dialog box provided by the user's browser/OS. For example,
<!-- (IE 10+, Edge (EdgeHTML), Edge (Chromium), Chrome, Firefox 42+) -->
<input type="file" accept=".xls,.xlsx" />
should provide a way to filter out files other than .xls or .xlsx. Although the MDN page for input element always said that it supports this, to my surprise, this didn't work for me in Firefox until version 42. This works in IE 10+, Edge, and Chrome.
So, for supporting Firefox older than 42 along with IE 10+, Edge, Chrome, and Opera, I guess it's better to use comma-separated list of MIME-types:
<!-- (IE 10+, Edge (EdgeHTML), Edge (Chromium), Chrome, Firefox) -->
<input type="file"
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel" />
[Edge (EdgeHTML) behavior: The file type filter dropdown shows the file types mentioned here, but is not the default in the dropdown. The default filter is All files (*).]
You can also use asterisks in MIME-types. For example:
<input type="file" accept="image/*" /> <!-- all image types -->
<input type="file" accept="audio/*" /> <!-- all audio types -->
<input type="file" accept="video/*" /> <!-- all video types -->
W3C recommends authors to specify both MIME-types and their corresponding extensions in the accept attribute. So, the best approach is:
<!-- Right approach: Use both file extensions and their corresponding MIME-types. -->
<!-- (IE 10+, Edge (EdgeHTML), Edge (Chromium), Chrome, Firefox) -->
<input type="file"
accept=".xls,.xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel" />
JSFiddle of the same: here.
Reference: List of MIME-types
IMPORTANT: Using the accept attribute only provides a way of filtering in the files of types that are of interest. Browsers still allow users to choose files of any type. Additional (client-side) checks should be done (using JavaScript, one way would be this), and definitely file types MUST be verified on the server, using a combination of MIME-type using both the file extension and its binary signature (ASP.NET, PHP, Ruby, Java). You might also want to refer to these tables for file types and their magic numbers, to perform a more robust server-side verification.
Here are three good reads on file-uploads and security.
EDIT: Maybe file type verification using its binary signature can also be done on client side using JavaScript (rather than just by looking at the extension) using HTML5 File API, but still, the file must be verified on the server, because a malicious user will still be able to upload files by making a custom HTTP request.
There is the accept attribute for the input tag. However, it is not reliable in any way.
Browsers most likely treat it as a "suggestion", meaning the user will, depending on the file manager as well, have a pre-selection that only displays the desired types. They can still choose "all files" and upload any file they want.
For example:
<form>
<input type="file" name="pic" id="pic" accept="image/gif, image/jpeg" />
</form>
Read more in the HTML5 spec
Keep in mind that it is only to be used as a "help" for the user to find the right files.
Every user can send any request he/she wants to your server.
You always have to validated everything server-side.
So the answer is: no you cannot restrict, but you can set a pre-selection but you cannot rely on it.
Alternatively or additionally you can do something similar by checking the filename (value of the input field) with JavaScript, but this is nonsense because it provides no protection and also does not ease the selection for the user. It only potentially tricks a webmaster into thinking he/she is protected and opens a security hole. It can be a pain in the ass for users that have alternative file extensions (for example jpeg instead of jpg), uppercase, or no file extensions whatsoever (as is common on Linux systems).
You can use the change event to monitor what the user selects and notify them at that point that the file is not acceptable. It does not limit the actual list of files displayed, but it is the closest you can do client-side, besides the poorly supported accept attribute.
var file = document.getElementById('someId');
file.onchange = function(e) {
var ext = this.value.match(/\.([^\.]+)$/)[1];
switch (ext) {
case 'jpg':
case 'bmp':
case 'png':
case 'tif':
alert('Allowed');
break;
default:
alert('Not allowed');
this.value = '';
}
};
<input type="file" id="someId" />
JSFiddle
Yes, you are right. It's impossible with HTML. User will be able to pick whatever file he/she wants.
You could write a piece of JavaScript code to avoid submitting a file based on its extension. But keep in mind that this by no means will prevent a malicious user to submit any file he/she really wants to.
Something like:
function beforeSubmit()
{
var fname = document.getElementById("ifile").value;
// check if fname has the desired extension
if (fname hasDesiredExtension) {
return true;
} else {
return false;
}
}
HTML code:
<form method="post" onsubmit="return beforeSubmit();">
<input type="file" id="ifile" name="ifile"/>
</form>
Technically you can specify the accept attribute (alternative in html5) on the input element, but it's not properly supported.
You can use the "accept" attribute as a filter in the file select box.
Using "accept" help you filter input files based on their "suffix" or their "MIME type"
1.Filter based on suffix:
Here "accept" attribute just allow to select files with .jpeg extension.
<input type="file" accept=".jpeg" />
2.Filter based on "file type"
Here the "accept" attribute just allows you to select a file with "image/jpeg" type.
<input type="file" accept="image/jpeg" />
Important: We can change or delete the extension of a file, without changing the meme type. For example it is possible to have a file without extension, but the type of this file can be "image/jpeg". So this file can not pass the accept=".jpeg" filter. but it can pass accept="image/jpeg".
3.We can use * to select all kinds of file types. For example below code allows to select all kinds of images. for example "image/png" or "image/jpeg" or ... . All of them are allowed.
<input type="file" accept="image/*" />
4.We can use cama ( , ) as an "or operator" in the select attribute. For example to allow all kind of images or pdf files we can use this code:
<input type="file" accept="image/* , application/pdf" />
Use input tag with accept attribute
<input type="file" name="my-image" id="image" accept="image/gif, image/jpeg, image/png" />
Click here for the latest browser compatibility table
Live demo here
To select only image files, you can use this accept="image/*"
<input type="file" name="my-image" id="image" accept="image/*" />
Live demo here
Only gif, jpg and png will be shown, screen grab from Chrome version 44
I know this is a bit late.
function Validatebodypanelbumper(theForm)
{
var regexp;
var extension = theForm.FileUpload.value.substr(theForm.FileUpload1.value.lastIndexOf('.'));
if ((extension.toLowerCase() != ".gif") &&
(extension.toLowerCase() != ".jpg") &&
(extension != ""))
{
alert("The \"FileUpload\" field contains an unapproved filename.");
theForm.FileUpload1.focus();
return false;
}
return true;
}
You could actually do it with javascript but remember js is client side, so you would actually be "warning users" what type of files they can upload, if you want to AVOID (restrict or limit as you said) certain type of files you MUST do it server side.
Look at this basic tut if you would like to get started with server side validation. For the whole tutorial visit this page.
Good luck!
As mentioned in previous answers we cannot restrict user to select files for only given file formats. But it's really handy to use the accept tag on file attribute in html.
As for validation, we have to do it at the server side. We can also do it at client side in js but its not a foolproof solution. We must validate at server side.
For these requirements I really prefer struts2 Java web application development framework. With its built-in file upload feature, uploading files to struts2 based web apps is a piece of cake. Just mention the file formats that we would like to accept in our application and all the rest is taken care of by the core of framework itself. You can check it out at struts official site.
I may suggest following:
If you have to make user select any of image files by default, the use accept="image/*"
<input type="file" accept="image/*" />
if you want to restrict to specific image types then use accept="image/bmp, image/jpeg, image/png"
<input type="file" accept="image/bmp, image/jpeg, image/png" />
if you want to restrict to specific types then use accept=".bmp, .doc, .pdf"
<input type="file" accept=".bmp, .doc, .pdf" />
You cannot restrict user to change file filer to all files, so always validate file type in script and server
Building on the previous answers of using the accept attribute, you can accomplish this using the File API. This also gives you access to the file contents should you use FileReader to do some local parsing or data handling.
First create an input element, here you could apply the file type to the accept attribute but for the example it will allow you to select all file types.
<input type="file" name="upload" accept="*" multiple>
Next we need to listen to the 'change' event on the input element.
var upload = document.querySelector('input[type="file"]');
upload.addEventListener('change', function() {});
Inside the function you'll be able to access the files object of the input.
var files = this.files
We can't just iterate over the object since it isn't an array, however we can use the item() function to access our File object from the list.
for (var i = 0; i < files.length; i++) {
var file = files.item(i);
}
Now that we have our File object, we can access its name and type properties and do our file check here. In this case I'm checking to see if it's a .txt file and printing a message if it isn't. You can check the name against a regex pattern for the file type or check the type against its MIME type.
if (!file.name.match(/.txt$/i) || file.type != 'text/plain') {
console.log(file.name + ' is not a .txt file.');
}
var upload = document.querySelector('input[type="file"]');
upload.addEventListener('change', function() {
var files = this.files;
for (var i = 0; i < files.length; i++) {
var file = files.item(i);
if (!file.name.match(/.txt$/i) || file.type != 'text/plain') {
console.log(file.name + ' is not a .txt file.');
}
}
});
<input type="file" name="upload" accept="*" multiple>
File API is very well supported for modern browsers. By combining this with the accept attribute you can easily filter what the local user can select in an upload and provide useful feedback. If you are uploading the file, you should still check and validate the file type on your backend.
I have a small ASP.NET Core site that I am hosting in AWS lambda. I have a simple Excel Workbook upload that is functioning correctly when hosted in an IIS Express instance (running from Visual Studio locally) but once my site is published to AWS Lambda, the binary file data is being corrupted. My code is below:
Upload.cshtml
<h3>Please pick the workbook to upload...</h3>
<div class="row">
<div class="col-md-4">
<form method="post" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="WorkbookFile" class="control-label"></label>
<input asp-for="WorkbookFile" type="file" class="form-control" style="height:auto" />
<span asp-validation-for="WorkbookFile" class="text-danger"></span>
</div>
<input type="submit" asp-page-handler="Preview" value="Preview Upload" class="btn btn-default" />
</form>
</div>
</div>
Upload.cshtml.cs
[BindProperty]
[Required]
[Display(Name = "Workbook")]
public IFormFile WorkbookFile { get; set; }
public async Task<IActionResult> OnPostPreview()
{
// Perform an initial check to catch FileUpload class
// attribute violations.
if (!ModelState.IsValid)
{
return Page();
}
byte[] fileContents;
Guid workbookId;
using (var memoryStream = new MemoryStream())
{
await WorkbookFile.OpenReadStream().CopyToAsync(memoryStream);
fileContents = memoryStream.ToArray();
workbookId = SaveWorkbook(fileContents);
}
return RedirectToPage("./UploadReview", new { id = workbookId });
}
As is outlined by the AWS documentation (https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html) I have added "multipart/form-data" to my Api Gateway's "binary" types list but this does not seem to be having any effect.
API Gateway Settings Screenshot
Based on everything I can find this should allow my uploaded files to be passed as binary straight to my service, however the resulting files are 2x the size of the files when uploaded locally, so I am assuming the base64 encoding is still being applied.
All of the Request headers appear to either be multipart/form-data or application/octet-stream so I'm really at the end of my rope as I am neither a Web nor an AWS expert. Any suggestions would be greatly appreciated. Thanks!
EDIT 1.
The picture (link) below shows log messages that write out the byte[] sizes I'm receiving in the ASP.NET core back-end when running local vs. running AWS.
Log messages with array sizes
The picture (link) below shows the Chrome dev tools showing my content-type is multipart/form-data and the length lines up with the expected size.
Chrome Dev Tools Information
After scouring much of the internet, my problem was "Save" doesn't mean "Use". The blog below gave me the hint I needed:
https://github.com/krisgholson/serverless-thumbnail
After changing the API Gateway's "Settings" to include the multipart/form-data and clicking "Save Changes", the changes are SAVED but are not being USED. In order to USE the new settings you must go to the Resources tab, "Actions" drop-down > Deploy API. Once the API has been deployed now the Saved configuration will be Used (in my scenario the binary data gets passed as expected). Many Thanks!
I've got some (badly written) legacy code that I have to modify but I'm having an odd problem with the prefixing of the controls.
The main page has several include files, including cpct_cost.aspx.
Within that included page is a control EquipmentCost:
<INPUT id="EquipmentCost" type="text" name="EquipmentCost" runat="server" size="10" onchange="TotalCost();" class="bk">
Another page references that control in client side javascript:
function TotalCost()
{
var a = document.CPCT_FORM.cpct_cost_EquipmentCost.value;
This is throwing a client error because when I run the code locally, the control is being named with a $ instead of an _:
<input name="cpct_cost$EquipmentCost" type="text" id="cpct_cost_EquipmentCost" size="10" onchange="TotalCost();" class="bk" />
And as a result, the client side javascript can't find the element in question.
The legacy code on the server handles this differently - the control name there is processed as:
<input name="cpct_cost:EquipmentCost" id="cpct_cost_EquipmentCost" type="text" size="10" onchange="TotalCost();" class="bk" />
and this is being processed correctly.
I thought at first it might be a local IIS setting, so I compiled my code and deployed it to the dev server, but the behavior was identical to my local execution.
I'm using what are supposed to be the latest source files, I haven't changed any project settings, so ... is there some way I can force the code from my machine to use the : instead of $? Or ... what am I missing?
The project is currently on the proposal list to be completely rearchitected, (so please, no suggestions to redesign the app ;) ) but in the mean time, I have a short term requirement to implement some minor new functionality in this ugly old beast, and I've got to get it done ASAP. What's frustrating is - I haven't changed these parts of the code at all, but the behavior is changing anyway.
UPDATE
Apparently the naming prefix used in at least .NET 1.1 is somewhat random, because after several builds, while I was trying various ways to work around this, the controls ended up getting the right name. So now I'm just not changing the code, which sucks because I really hate taking this "freeze it while it's randomly right" approach.
You could pass in a reference to the input control as a parameter to the JS function, ie:
<INPUT id="EquipmentCost" type="text" name="EquipmentCost" runat="server" size="10" onchange="TotalCost(this);" class="bk">
function TotalCost(txtEquipCost) {
var a = txtEquipCost.value;
}
Then it doesn't matter what the id is.
EDIT:
If you have more controls, create JS variables on the page, eg:
var txtEquipCost = document.getElementById('<%= YourControl.ClientID %>');
var txtOtherCost = document.getElementById('<%= YourOtherControl.ClientID %>');
Then the onChange function call could be TotalCost(txtEquipCost, txtOtherCost)
EDIT2:
See this question about ClientId and UniqueId which may be useful:
C# asp.net Why is there a difference between ClientID and UniqueID?
You could change your Javascript to use the id that is getting generated.
function TotalCost()
{
var a = document.getElementById('<%= YourControl.ClientID %>').value;
}
Also if you need absolute control over the generated id of that control it turns out that in asp.net 4.0 the ClientIDMode property was introduced so that developers have more control over how that id is generated.
Check out these two sources
http://msdn.microsoft.com/en-us/library/system.web.ui.control.clientid.aspx
http://weblogs.asp.net/asptest/archive/2009/01/06/asp-net-4-0-clientid-overview.aspx