Mail Sending Program & Request Timeout - http

I was asked to develop an auto-mail sending program on asp.net. It is supposed to send, say 5000 e-mails reading the addresses from a database. It will sure fall into request timeout tough. So it seems I have to convert it into a windows app. But I'd like to know if ajaxifying this web-app would help. If I write a web service, and my web app sends the mail addresses as lists of 50 at a time. when done, send the next 50 and so on. Would this help to solve the problem of http request timeout?

Using a webservice endpoint to send your emails is a good idea, whether you call it from an aspx class or from the client with javascript.
Simply use the webservice call to spawn a thread to send the emails and return immediately.
If you wanted visual progress cues then write another ajax endpoint or aspx page that will display the status of the email thread's progress.
There are many ways to accomplish this, you should be able to come up with one with the information given.
Batching from ajax is probably going to be more work than you want to do and adds unnecessary complexity (which is never a good thing).
this is interesting. I may spike this and post some code.
Ok, im back. here ya go. both a webform ui and an ajax ui.
None of this is meant to be finished product - is a spike to support a story. bend/fold/spindle at will.
EmailService.asmx
using System;
using System.ComponentModel;
using System.Threading;
using System.Web.Script.Services;
using System.Web.Services;
namespace EmailSendingWebApplication
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class EmailService : WebService
{
private static EmailSendingProgress _currentProgress;
private static Thread _emailThread;
/// <summary>
///
/// </summary>
/// <param name="criteria">just an example</param>
/// <param name="anotherCriteria">just an example</param>
/// <returns></returns>
[WebMethod]
public EmailSendingProgress SendEmails(string criteria, int anotherCriteria)
{
try
{
if (_currentProgress != null && _emailThread.IsAlive)
{
throw new InvalidOperationException(
"Email batch is already in progress. Wait for completion or cancel");
}
// use your criteria to cue up the emails to be sent.
// .....
// and derive a way for a thread to identify the emails
// i am using a batchid
int batchId = 1000; // contrived
// create a thread
_emailThread = new Thread(ProcessEmails);
_currentProgress = new EmailSendingProgress
{
Status = ProcessState.Starting,
BatchId = batchId
};
// you could use a 'state' object but this process/thread
// is single use/single instance just access _currentProgress
// by the static member
_emailThread.Start();
return _currentProgress;
}
catch (Exception ex)
{
_currentProgress = new EmailSendingProgress
{
Status = ProcessState.Error,
Message = "Error starting process:" + ex.Message
};
}
return _currentProgress;
}
[WebMethod]
public EmailSendingProgress CancelEmailProcess()
{
if (_currentProgress != null && _emailThread.IsAlive)
{
_currentProgress.Cancel = true;
_currentProgress.Message = "Cancelling";
}
return _currentProgress;
}
[WebMethod]
public EmailSendingProgress GetProgress()
{
return _currentProgress;
}
private static void ProcessEmails()
{
// process your emails using the criteria, in this case,
// a batchId
int totalEmails = 100;
int currentEmail = 0;
lock (_currentProgress)
{
_currentProgress.Total = totalEmails;
_currentProgress.Status = ProcessState.Processing;
}
for (; currentEmail < totalEmails; currentEmail++)
{
lock (_currentProgress)
{
if (_currentProgress.Cancel)
{
_currentProgress.Status = ProcessState.Cancelled;
_currentProgress.Message = "User cancelled process.";
break;
}
_currentProgress.Current = currentEmail + 1;
}
try
{
// send your email
Thread.Sleep(100); // lallalala sending email
}
catch (Exception ex)
{
// log the failure in your db
// then check to see if we should exit on error
// or just keep going.
lock (_currentProgress)
{
if (_currentProgress.CancelBatchOnSendError)
{
_currentProgress.Status = ProcessState.Error;
_currentProgress.Message = ex.Message;
break;
}
}
}
}
{
// don't want to obscure state/message from abnormal
// termination..
if (_currentProgress.Status == ProcessState.Processing)
{
_currentProgress.Status = ProcessState.Idle;
_currentProgress.Message = "Processing complete.";
}
}
}
}
public enum ProcessState
{
Idle,
Starting,
Processing,
Cancelled,
Error
}
[Serializable]
public class EmailSendingProgress
{
public int BatchId;
public bool Cancel;
public bool CancelBatchOnSendError;
public int Current;
public string Message;
public ProcessState Status;
public int Total;
}
}
WebFormUI.aspx
<%# Page Language="C#" %>
<%# Import Namespace="EmailSendingWebApplication" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
var svc = new EmailService();
UpdateProgress(svc.GetProgress());
}
protected void SendEmailsButton_Click(object sender, EventArgs e)
{
// arbitrary params - modify to suit
string criteria = string.Empty;
int anotherCriteria = 0;
var svc = new EmailService();
UpdateProgress(svc.SendEmails(criteria, anotherCriteria));
}
protected void CancelEmailProcessButton_Click(object sender, EventArgs e)
{
var svc = new EmailService();
UpdateProgress(svc.CancelEmailProcess());
}
private void UpdateProgress(EmailSendingProgress progress)
{
SetButtonState(progress);
DisplayProgress(progress);
}
private void DisplayProgress(EmailSendingProgress progress)
{
if (progress != null)
{
EmailProcessProgressLabel.Text = string.Format("Sending {0} of {1}", progress.Current, progress.Total);
EmailProcessStatusLabel.Text = progress.Status.ToString();
EmailProcessMessageLabel.Text = progress.Message;
}
else
{
EmailProcessProgressLabel.Text = string.Empty;
EmailProcessStatusLabel.Text = string.Empty;
EmailProcessMessageLabel.Text = string.Empty;
}
}
private void SetButtonState(EmailSendingProgress progress)
{
if (progress != null &&
(progress.Status == ProcessState.Starting || progress.Status == ProcessState.Processing))
{
CancelEmailProcessButton.Visible = true;
SendEmailsButton.Visible = false;
}
else
{
CancelEmailProcessButton.Visible = false;
SendEmailsButton.Visible = true;
}
}
protected void RefreshButton_Click(object sender, EventArgs e)
{
// noop just to get postback. you could also use meta headers to refresh the page automatically
// but why?
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<br />
EmailProcessStatus:
<asp:Label ID="EmailProcessStatusLabel" runat="server" Text="EmailProcessStatus"></asp:Label>
<br />
EmailProcessProgress:
<asp:Label ID="EmailProcessProgressLabel" runat="server" Text="EmailProcessProgress"></asp:Label>
<br />
EmailProcessMessage:<asp:Label ID="EmailProcessMessageLabel" runat="server" Text="EmailProcessMessage"></asp:Label>
<br />
<br />
<asp:Button ID="SendEmailsButton" runat="server" OnClick="SendEmailsButton_Click"
Text="Send Emails" />
<asp:Button ID="CancelEmailProcessButton" runat="server" OnClick="CancelEmailProcessButton_Click"
Text="Cancel Email Process" />
<br />
<br />
<asp:Button ID="RefreshButton" runat="server" OnClick="RefreshButton_Click" Text="Refresh" />
</div>
</form>
</body>
</html>
AjaxUI.htm
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
//http://www.codeproject.com/Articles/38999/Consuming-ASP-net-WebServices-WCF-Services-and-sta.aspx
var ProcessState = ["Idle", "Starting", "Processing", "Cancelled", "Error"];
function createXHR() {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
else {
throw new Error("Could not create XMLHttpRequest object.");
}
return xhr;
}
function emailAjax(operation, postData, callback) {
var xhr = createXHR();
xhr.open("POST", "EmailService.asmx/" + operation, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
callback(xhr.responseText);
}
};
xhr.setRequestHeader("content-type", "application/json");
xhr.send(postData);
}
function $(id) {
var e = document.getElementById(id);
return e;
}
function startProcess() {
var postData = '{"criteria" : "something", "anotherCriteria" : "1"}';
emailAjax("SendEmails", postData, displayProgress);
}
function cancelProcess() {
emailAjax("CancelEmailProcess", null, displayProgress);
}
function getProgress() {
emailAjax("GetProgress", null, displayProgress);
}
function displayProgress(json) {
eval('var result=' + json + '; var prg=result.d;');
$("EmailProcessMessage").innerHTML = "";
$("EmailProcessStatus").innerHTML = "";
$("EmailProcessProgress").innerHTML = "";
$("CancelEmailProcessButton").style.display = "none";
$("SendEmailsButton").style.display = "none";
if (prg) {
$("EmailProcessMessage").innerHTML = prg.Message;
$("EmailProcessStatus").innerHTML = ProcessState[prg.Status];
$("EmailProcessProgress").innerHTML = "Sending " + prg.Current + " of " + prg.Total;
}
if (prg && (prg.Status == 1 || prg.Status == 2)) {
$("SendEmailsButton").style.display = "none";
$("CancelEmailProcessButton").style.display = "inline";
}
else {
$("CancelEmailProcessButton").style.display = "none";
$("SendEmailsButton").style.display = "inline";
}
}
function init() {
$("SendEmailsButton").onclick = startProcess;
$("CancelEmailProcessButton").onclick = cancelProcess;
// kinda quick but we are only proccing 100 emails for demo
window.setInterval(getProgress, 1000);
}
</script>
</head>
<body onload="init()">
EmailProcessStatus:<span id="EmailProcessStatus"></span><br />
EmailProcessProgress:<span id="EmailProcessProgress"></span><br />
EmailProcessMessage:<span id="EmailProcessMessage"></span><br />
<input type="button" id="SendEmailsButton" value="SendEmails" style="display: none" />
<input type="button" id="CancelEmailProcessButton" value="CancelEmailProcess" style="display: none" />
</body>
</html>

So user will have to leave the browser window open until all the e-mails are sent? Does not sound very good. I would solve this using a daemon or simple script that is run by cron (and checks db if there is something to send), on Windows I hope you can do something similar (write Windows service etc.). This is a purely server-side task, I think ajaxifying it shows that author of the web app wasn't able to make it in a better way, it may even make your web app to be mentioned on thedailywtf.com :)

Related

Validating forms only on submit with Blazor

I've recently started using Blazor. Is there a way to trigger form model validation only on submit, instead of live on each change?
Just for clarification, let's say I have something like this:
<EditForm Model="this" OnValidSubmit="SubmitForm">
<DataAnnotationsValidator />
<ValidationSummary />
<Label For="Name">Name</Label>
<InputText id="Name" name="Name" class="form-control" #bind-Value="Name"/>
<button type="submit">Save</button>
</EditForm>
#code {
[StringLength(10, ErrorMessage="Name too long")]
public string Name { get; set; }
private async Task SubmitForm()
{
// ...
// send a POST request
}
}
By default, it seems like the validity of the field and the error messages displayed in the ValidationSummary get re-evaluated on every change of the text input (e.g. as soon as I delete the 11th character from the input, the "too long" message disappears).
I would prefer if the displayed messages would remain frozen until the Submit button is clicked.
I suppose it would be possible to implement it by removing the ValidationSummary component and implementing a custom solution (e.g. displaying a List of error messages that's refreshed only on submit), but I was wondering if there is some idiomatic solution that I'm not aware of.
When validation occurs is controlled by the Validator you're using.
There are two events that you can receive from EditContext:
OnValidationRequested is invoked either when EditContext.Validate is called or as part of the form submission process.
OnFieldChanged is invoked every time a field value is changed.
A validator uses these events to trigger it's validation process, and outputs the results to the EditContext's ValidationMessageStore.
DataAnnotationsValidator wires up for both events and triggers validation whenever either is invoked.
There are other validators out there, and writing your own is not too difficult. Other than those from the usual control suppliers, there's Blazored, or mine. Mine is documented here - https://shauncurtis.github.io/articles/Blazor-Form-Validation.html. it has a DoValidationOnFieldChange setting!
#enet's answer sparked an alternative answer. Build your own DataAnnotationsValidator.
Here's the EditContext Extensions code. It's a modified version of the original MS Code with some extra control arguments.
using Microsoft.AspNetCore.Components.Forms;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
namespace StackOverflowAnswers;
public static class EditContextCustomValidationExtensions
{
public static IDisposable EnableCustomValidation(this EditContext editContext, bool doFieldValidation, bool clearMessageStore)
=> new DataAnnotationsEventSubscriptions(editContext, doFieldValidation, clearMessageStore);
private static event Action? OnClearCache;
private static void ClearCache(Type[]? _)
=> OnClearCache?.Invoke();
private sealed class DataAnnotationsEventSubscriptions : IDisposable
{
private static readonly ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo?> _propertyInfoCache = new();
private readonly EditContext _editContext;
private readonly ValidationMessageStore _messages;
private bool _doFieldValidation;
private bool _clearMessageStore;
public DataAnnotationsEventSubscriptions(EditContext editContext, bool doFieldValidation, bool clearMessageStore)
{
_doFieldValidation = doFieldValidation;
_clearMessageStore = clearMessageStore;
_editContext = editContext ?? throw new ArgumentNullException(nameof(editContext));
_messages = new ValidationMessageStore(_editContext);
if (doFieldValidation)
_editContext.OnFieldChanged += OnFieldChanged;
_editContext.OnValidationRequested += OnValidationRequested;
if (MetadataUpdater.IsSupported)
{
OnClearCache += ClearCache;
}
}
private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs)
{
var fieldIdentifier = eventArgs.FieldIdentifier;
if (TryGetValidatableProperty(fieldIdentifier, out var propertyInfo))
{
var propertyValue = propertyInfo.GetValue(fieldIdentifier.Model);
var validationContext = new ValidationContext(fieldIdentifier.Model)
{
MemberName = propertyInfo.Name
};
var results = new List<ValidationResult>();
Validator.TryValidateProperty(propertyValue, validationContext, results);
_messages.Clear(fieldIdentifier);
foreach (var result in CollectionsMarshal.AsSpan(results))
{
_messages.Add(fieldIdentifier, result.ErrorMessage!);
}
// We have to notify even if there were no messages before and are still no messages now,
// because the "state" that changed might be the completion of some async validation task
_editContext.NotifyValidationStateChanged();
}
}
private void OnValidationRequested(object? sender, ValidationRequestedEventArgs e)
{
var validationContext = new ValidationContext(_editContext.Model);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(_editContext.Model, validationContext, validationResults, true);
// Transfer results to the ValidationMessageStore
_messages.Clear();
foreach (var validationResult in validationResults)
{
if (validationResult == null)
{
continue;
}
var hasMemberNames = false;
foreach (var memberName in validationResult.MemberNames)
{
hasMemberNames = true;
_messages.Add(_editContext.Field(memberName), validationResult.ErrorMessage!);
}
if (!hasMemberNames)
{
_messages.Add(new FieldIdentifier(_editContext.Model, fieldName: string.Empty), validationResult.ErrorMessage!);
}
}
_editContext.NotifyValidationStateChanged();
}
public void Dispose()
{
if (_clearMessageStore)
_messages.Clear();
if (_doFieldValidation)
_editContext.OnFieldChanged -= OnFieldChanged;
_editContext.OnValidationRequested -= OnValidationRequested;
_editContext.NotifyValidationStateChanged();
if (MetadataUpdater.IsSupported)
{
OnClearCache -= ClearCache;
}
}
private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
{
var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName);
if (!_propertyInfoCache.TryGetValue(cacheKey, out propertyInfo))
{
// DataAnnotations only validates public properties, so that's all we'll look for
// If we can't find it, cache 'null' so we don't have to try again next time
propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);
// No need to lock, because it doesn't matter if we write the same value twice
_propertyInfoCache[cacheKey] = propertyInfo;
}
return propertyInfo != null;
}
internal void ClearCache()
=> _propertyInfoCache.Clear();
}
}
And the CustomValidation component:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace StackOverflowAnswers;
public class CustomValidation : ComponentBase, IDisposable
{
private IDisposable? _subscriptions;
private EditContext? _originalEditContext;
[CascadingParameter] EditContext? CurrentEditContext { get; set; }
[Parameter] public bool DoEditValidation { get; set; } = false;
/// <inheritdoc />
protected override void OnInitialized()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(DataAnnotationsValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(DataAnnotationsValidator)} " +
$"inside an EditForm.");
}
_subscriptions = CurrentEditContext.EnableCustomValidation(DoEditValidation, true);
_originalEditContext = CurrentEditContext;
}
/// <inheritdoc />
protected override void OnParametersSet()
{
if (CurrentEditContext != _originalEditContext)
{
// While we could support this, there's no known use case presently. Since InputBase doesn't support it,
// it's more understandable to have the same restriction.
throw new InvalidOperationException($"{GetType()} does not support changing the " +
$"{nameof(EditContext)} dynamically.");
}
}
/// <inheritdoc/>
protected virtual void Dispose(bool disposing)
{
}
void IDisposable.Dispose()
{
_subscriptions?.Dispose();
_subscriptions = null;
Dispose(disposing: true);
}
}
You can use it like this:
<EditForm EditContext=this.editContext OnValidSubmit=OnValidSubmit>
<CustomValidation DoEditValidation=false/>
#*<DataAnnotationsValidator/>*#
<div class="row">
<div class="col-2">
Date:
</div>
<div class="col-10">
<InputDate #bind-Value=this.Record.Date></InputDate>
</div>
</div>
.......

Page methods not calling web service methods sometimes

In ASP.NET, Unable to invoke delIt web service method in Defaults.aspx.cs from ActionComplete method. But am able to invoke AddIt and UpdateIt web methods in Defaults.aspx.cs
My code:
In Default.aspx:
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True" />
function ActionComplete(args) {
var ganttRecord = args.data;
if (args.requestType === 'save' && args._cAddedRecord) {
PageMethods.AddIt(ganttRecord);
}
else if (args.requestType === 'save') {
PageMethods.UpdateIt(ganttRecord);
}
else if (args.requestType === 'delete') {
PageMethods.delIt(ganttRecord);
}
}
In Default.aspx.cs:
[WebMethod]
public static void AddIt(TaskData record)
{
Default sample = new Default();
sample.Add(record);
}
[WebMethod]
public static void UpdateIt(TaskData record)
{
Default sample1 = new Default();
sample1.Update(record);
}
[WebMethod]
public static void delIt(TaskData record)
{
Default sample2 = new Default();
sample2.Delete(record);
}
The "ganttRecord" Json object which i passed to delIt method has some extra undefined variables and boolean variables. So only I think so Pagemethods unable to call that delIt method in the Defaults.aspx.cs

How to check file size of each file before uploading multiple files in ajaxtoolkit ajaxfileupload control in asp.net?

<cc1:AjaxFileUpload ID="AjaxFileUpload1" AllowedFileTypes="jpg,jpeg"
runat="server" MaximumNumberOfFiles="4" OnUploadComplete="AjaxFileUpload1_UploadComplete"
/>
Code behind file
protected void AjaxFileUpload1_UploadComplete(object sender, AjaxControlToolkit.AjaxFileUploadEventArgs e)
{
if (e.FileSize > 10)
{
string filePath = e.FileName;
AjaxFileUpload1.SaveAs(Server.MapPath(filePath));
}
else
{
}
}
I want to check that all the files size should not exceed a particular value before the files upload event.
Try this way:
Server side:
protected void AjaxFileUpload1_UploadComplete(object sender, AjaxControlToolkit.AjaxFileUploadEventArgs e)
{
try
{
string savePath = MapPath("~/Images/" + e.FileName);
// dont save file & return if condition not matched.
if (e.FileSize > 72000) // use same condition in client side code
{
return;
}
AjaxFileUpload1.SaveAs(savePath);
}
catch (Exception ex)
{
throw ex;
}
}
and on client side:
<script type="text/javascript">
function UploadComplete(sender, args) {
var filesize = args.get_fileSize();
var fileId = args.get_fileId();
var status = document.getElementById('AjaxFileUpload1_FileItemStatus_' + fileId);
var container = document.getElementById('AjaxFileUpload1_FileInfoContainer_' + fileId);
if (filesize > 72000) { // same condition used for server side
document.getElementById('lblStatus').innerText = "error";
if (status.innerText) {
status.innerText = " (Error)";
}
if (status.textContent) {
status.textContent = " (Error)";
}
container.style.color = 'Red';
}
}
</script>
<cc1:AjaxFileUpload ID="AjaxFileUpload1" AllowedFileTypes="jpg,jpeg" runat="server" MaximumNumberOfFiles="4" OnUploadComplete="AjaxFileUpload1_UploadComplete" OnClientUploadComplete="UploadComplete" />
Hope this helps!!
<script type="text/javascript">
$(".ajax__fileupload_dropzone").bind("drop", function () {
checkfilesize();
});
$(".ajax__fileupload_queueContainer").bind("click", function () {
checkfilesize();
});
$(".ajax__fileupload_uploadbutton").bind("mouseenter", function () {
checkfilesize();
});
function checkfilesize() {
var total_filesize_num = 0;
var myElements = $(".filesize");
if (myElements.length == 0) {
$(".ajax__fileupload_uploadbutton").css("visibility", "hidden");
return;
}
for (var i = 0; i < myElements.length; i++) {
var filesize = myElements.eq(i).html(); //$(".filesize").html();
total_filesize_num = total_filesize_num + filesize_tonum(filesize);
}
if (total_filesize_num > 5) {
$(".ajax__fileupload_uploadbutton").css("visibility", "hidden");
alert('Maximum file size is 5MB only! Please select another one.');
return;
} else {
$(".ajax__fileupload_uploadbutton").css("visibility", "visible");
}
}
function countsumfilesize() {
var sumfilesize = 0;
var myElements = $(".filesize");
for (var i = 0; i < myElements.length; i++) {
alert(myElements.eq(i).html());
}
}
function filesize_tonum(filesize) {
var filesize_num = 0;
if (filesize.indexOf("kb") > 0) {
var space = filesize.lastIndexOf(" ");
filesize_num = parseFloat("0." + filesize.substr(0, filesize.length - space + 1));
}
else if (filesize.indexOf("MB") > 0) {
var space = filesize.lastIndexOf(" ");
filesize_num = parseFloat(filesize.substr(0, filesize.length - space + 1));
}
return filesize_num;
}
</script>
<ajaxToolkit:AjaxFileUpload ID="AjaxFileUploadImage" runat="server" OnClientUploadComplete="uploadComplete" MaximumNumberOfFiles="1" AllowedFileTypes="gif,png,jpg,jpeg" onchange="checkfilesize(); return false;" />
See the code below:
public void afuUpload_UploadedComplete(object sender, AsyncFileUploadEventArgs e)
{
try
{
string savePath = MapPath("~/Uploads/" + Path.GetFileName(e.filename));
if (int.Parse(e.filesize) > 3000000)
{
return;
}
afuUpload.SaveAs(savePath);
}
catch (Exception ex)
{
throw ex;
}}
The idea is to prevent the file is uploaded to the server. In the proposed solution, when the flow code has reached afuUpload_UploadedComplete, the file was uploaded to server, but has not yet been recorded in the path you specify. For example, if the limit is 20 megabytes and the selected file is 22 megabytes, when the code reaches afuUpload_UploadedComplete, 22 Megabytes already been uploaded to the server.
The solution sought is that the validation is done on the client side (JavaScript) and that prevents the code arrives to CodeBehind on the server.
In my case, I tried to OnClientUploadComplete generating an exception when the file size limit is exceeded, but it did not work and the code is still reaching the CodeBehind. The other problem is that when the exception occurs, the JavaScript function OnClientUploadError is not firing to intercept the exception generated in OnClientUploadComplete function.
Controller
[HttpPost]
public ActionResult Create(string Album, Photo photo, IEnumerable<HttpPostedFileBase> files, DateTime? datec, string NewAlbum = null)
{
.....
foreach (var file in files)
{
decimal sum = file.ContentLength / 1048;
if (sum > 4000)
{
errorlist2 += "Sorry " + file.FileName + " has exceeded its file limit off 4 MB <br/>";
}
}
if (errorlist2 != "")
{
ViewBag.Error = errorlist2;
return View(photo);
}
// we dont want put the message in the loop it will come out on first max limit , rather find all files in excess, then we can pass the message
//also make sure your web config is set for a limit on max size
//using the normal html multi uploaded
// <input type="file" name="files" id="files" required multiple="multiple" accept=".jpg, .png, .gif" size="4" />

Error messages in ASP.NET with jQuery UI

I've been using my own Error reporting module which was combination of simple c# and jQueryUI Dialog. Problem is that once error or success occurs i do write it's value to session. It does work pretty good on pages with Responce.Redirect on error but not on pages where i catch an error and then return to same form.
My question is why does session which added pre-postback fails to load in pages where i have return statement on some condition.
And if there another way to save errors and success message except in session ? Maybe global variables or something like that ...
CODE EXAMPLES
this is Error class
public static string getMessage()
{
HttpContext c = HttpContext.Current;
string messageType = "";
if (c.Session["errorMessage"] != null)
{
messageType = "errorMessage";
}
else if (c.Session["successMessage"] != null)
{
messageType = "successMessage";
}
if (!string.IsNullOrEmpty(messageType))
{
string[] messageBody = c.Session[messageType].ToString().Split('|');
StringBuilder userMessageSb = new StringBuilder();
userMessageSb.Append(string.Format("<div id=\"{0}\" title=\"{1}\">{2}</div>", messageType, messageBody[0], messageBody[1]));
// fix so message will not re-appear
c.Session.Remove(messageType);
messageType = userMessageSb.ToString();
}
return messageType;
}
public static void setSuccess(string successMessage)
{
HttpContext.Current.Session["successMessage"] = setMessage("success", successMessage);
}
public static void setError(string errorMessage)
{
HttpContext.Current.Session["errorMessage"] = setMessage("error", errorMessage);
}
private static string setMessage(string messageTitle, string messageBody)
{
return string.Format("{0}|{1}", messageTitle, messageBody);
}
i set message like this prior to redirect or return
Errors.setError(my error is");
i get error on bottom of my masterpage like this
<%= Errors.getMessage() %>
and this is JS
$(function () {
$("#errorMessage").dialog("destroy");
$("#successMessage").dialog("destroy");
if ($("#errorMessage").length != 0) {
$("#errorMessage").dialog({
modal: true,
height: 300,
width: 400,
buttons: {
Ok: function () {
$(this).dialog('close');
}
}
});
}
if ($("#successMessage").length != 0) {
$("#successMessage").dialog({
modal: true,
height: 300,
width: 400,
buttons: {
Ok: function () {
$(this).dialog('close');
}
}
});
}
});
There is a possibility that <%= Errors.getMessage() %> executes before you call Errors.setError(my error is") in case when you are not redirecting.
Hope below answer helps.
Create a property in your master page code behind
public string MessagePlaceholder
{
get { return messagePlaceholder.InnerHtml; }
set { messagePlaceholder.InnerHtml = value; }
}
Replace <%= Errors.getMessage() %> with a div place holder like below
<div id="messagePlaceholder" runat="server"></div>
And here is your setError method
public static void setError(string errorMessage, bool redirecting)
{
HttpContext.Current.Session["errorMessage"] = setMessage("error", errorMessage);
if (!redirecting)
{
((HttpContext.Current.Handler as System.Web.UI.Page).Master as YourMasterPageType).MessagePlaceholder = getMessage();
}
}
EDIT
Sorry I forgot this
In Page_Load event of your master page
if(!IsPostBack)
{
messagePlaceholder.InnerHtml = Errors.getMessage();
}

ASP:TextBox Value disappears in postback only when password

I have an asp.net textbox like this:
<asp:TextBox ID="PINPad" runat="server" Columns="6" MaxLength="4"
CssClass="PINTextClass"></asp:TextBox>
It is, as you might have guessed, the text box from an on screen PIN pad. Javascript fills in the values. The page is posted back every five seconds (using an update panel if that matters) to update various other unrelated items on the screen. This works just fine.
However, when I convert it to a password text box, like this:
<asp:TextBox ID="PINPad" runat="server" Columns="6" MaxLength="4"
CssClass="PINTextClass" TextMode="Password"></asp:TextBox>
Then whenever the page posts back, the text box is cleared out on the screen and the textbox is empty (though during the timer event, the value does make it back to the server.)
Any suggestions how to fix this, so that it retains its value during postback?
As a security feature, ASP.NET tries to disallow you from sending the password value back to the client. If you're okay with the security issues (i.e. it's either not really secure information or you're sure that the connection is secure), you can manually set the "value" attribute of the control, rather than using its Text property. It might look something like this:
this.PINPad.Attributes.Add("value", this.PINPad.Text);
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
if (!(String.IsNullOrEmpty(txtPwd.Text.Trim())))
{
txtPwd.Attributes["value"]= txtPwd.Text;
}
if (!(String.IsNullOrEmpty(txtConfirmPwd.Text.Trim())))
{
txtConfirmPwd.Attributes["value"] = txtConfirmPwd.Text;
}
}
}
here is another way to do it:-
using System;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebControlLibrary
{
public class PWDTextBox : TextBox
{
public PWDTextBox()
{
this.TextMode = TextBoxMode.Password;
}
public string Password
{
get
{
string val = (string)ViewState["pwd"];
if (string.IsNullOrEmpty(val))
{
return "";
}
else
{
return val;
}
}
set
{
ViewState["pwd"] = value;
}
}
public override string Text
{
get
{
return Password;
}
set
{
Password = value;
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.Text = Password;
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Password);
}
}
}
The problem of losing the password in the postback can be avoid making use of Asynchronous JavaScript calls, lets describe a typical scenario for a Login page:
Lets say we have a Login page which allows the user to change the language of its labels when the user choose a language with a dropdownlist
a solution would be to invoke selectedIndexChanged event of the dropdownlist, make a postback which goes to the server and picks up the labels in the chosen language.
in this scenario the field password will be lost due to the security feature of ASP.NET which makes passwords fields not persisted between a postbacks.
This scenario can be solved if the postback is avoided making use of Asynchronous JavaScript Technology and XML (Ajax) calls.
Add a javascript function which will be invoked from the dropdownlist control, in this case this function is assigned to the Command property of the dropdownlist in code behind:
function ValueChanged(div)
{
var table = div.getElementsByTagName("table");
if (table && table.length > 0)
{
var t = table[0].getAttribute('type');
if (t != null && (t == "DropDown"))
{
var inputs = div.getElementsByTagName("input");
if (inputs && inputs.length == 2)
{
{
Translate(inputs[1].value);
}
}
}
}
}
The Translate function takes as parameter the selected option language in the dropdown control and performs the asynchronous call as shown bellow.
function Translate(lang)
{
var request = null;
if (window.XMLHttpRequest)
{
request = new XMLHttpRequest();
if (request.overrideMimeType)
{
request.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject)
{
request = new ActiveXObject("Msxml2.XMLHTTP");
}
if (request == null)
{
return;
}
var url = "GetLoginTranslations.aspx";
request.open('GET', url +'?lang=' + lang, true);
request.setRequestHeader("Cache-Control", "no-cache");
request.setRequestHeader("Pragma", "no-cache");
request.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
request.onreadystatechange = function () { TranslateLabels(request); };
request.send(null);
}
the function Translate shown above performs the call and get the results in the specified .aspx page (in this case "GetLoginTranslations.aspx")
when the request is completed and the request.onreadystatechange is set to the function TranslateLabels this function will be executed.
on this way the postback is not executed as before in the event onSelectedIndexChanged of the dropdownlist control.
the TranslateLabels function would look something like :
function TranslateLabels(request)
{
if (request.readyState == 4)
{
if (request.status == 200)
{
if (request.responseXML)
{
var objRoot = request.responseXML.documentElement;
if (objRoot)
{
if (objRoot.nodeName == "strings")
{
for (var i = 0; i < objRoot.childNodes.length; i++)
{
var node = objRoot.childNodes[i];
var elem;
switch (node.getAttribute("id"))
{
case "lbl_login":
elem = document.getElementById("lbl_login");
if (elem)
elem.innerHTML = node.firstChild.nodeValue;
break;
}
///....
}
}
}
}
}
}
the request.responseXML contains the XML built in the page GetLoginTranslations.aspx and the structure of this XML is defined there.
the Page_Load() event in the GetLoginTranslations.aspx should look like:
protected void Page_Load(object sender, EventArgs e)
{
if (Request["lang"] != null)
strLang = Request["lang"];
//init response
Response.Clear();
Response.Cache.SetExpires(DateTime.Now);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetValidUntilExpires(true);
Response.ContentType = "application/xml";
Response.Charset = "utf-8";
XmlTextWriter xml = new XmlTextWriter(Response.OutputStream, System.Text.Encoding.UTF8)
{
Formatting = Formatting.None
};
xml.WriteStartDocument();
xml.WriteStartElement("strings");
xml.WriteStartElement("string");
xml.WriteAttributeString("id", "lbl_login");
xml.WriteString(GetTranslation("label_login", strLang));
xml.WriteEndElement();
// ... the other labels
xml.WriteEndElement(); //</strings>
xml.Close();
}
Some other considerations:
set the the property AutoPostback of the dropdownlist to false.
Happens both for view-model properties named 'Password' and 'PIN'. You can bypass the behavior by defining those as:
string Password ;
... rather than:
string Password { get; set; }
If you do so, features such the 'LabelFor' macro displaying 'DisplayAttribute.Name' no longer works, so you'd have to define those directly in the HTML.
Or you can simply name the fields something other than 'Password' or 'PIN'.

Resources