Friday, February 22, 2019

[SOLVED] "Wildcards in this project type are not currently supported" error in Wix projects.

I encountered an error "Wildcards in this project type are not currently supported" when I was trying to modify wixproj file to include BeforeBuild section to copy files from NuGet packages folder into project root directory.
Initially, I was referencing a Visual Studio documentation for a Copy MSBuild task and created the following snippet:
   
   <ItemGroup>
      <ServiceFiles Include="..\packages\ServiceArtifacts.1.0.1\content\**\*.*"/>
   </ItemGroup>

   <Target Name="BeforeBuild">
      <Copy
        SourceFiles="@(ServiceFiles )"
        DestinationFiles="@(ServiceFiles ->'service\%(RecursiveDir)%(Filename)%(Extension)')"
       />
    </Target> 
Immediately, I got the "wildcards not supported..." error when reloading project in Visual Studio. After a bit of research I updated the snippet as follows:
<PropertyGroup>
  <ServiceFolder>..\packages\ServiceArtifacts.1.0.1\content</ServiceFolder>
</PropertyGroup>

<Target Name="BeforeBuild">
  <CreateItem Include="$(ServiceFolder)\**\*.*">
    <Output TaskParameter="Include" ItemName="ServiceFiles" />
  </CreateItem>
  <Copy
  SourceFiles="@(ServiceFiles)"
  DestinationFiles="@(ServiceFiles->'service\%(RecursiveDir)%(Filename)%(Extension)')"
        />
</Target>
Save. Reload project file. Build.
Voila!
Everything is working now.

Wednesday, April 22, 2009

Brewer Machine Company website

I recently have finished a website for Brewer Machine Company. The site is ASP.NET based and uses script.aculo.us framework for some of the "special effects". Custom build CMS is included as well... :)
http://www.brewermachine.net/

Friday, March 27, 2009

ASP.NET Javascript Image Upload Preview (Take 2)

Recently I had to revisit Image Upload Preview techniques for one of my projects. This time around I was hoping to make use of AJAX framework, and avoid using "hidden" postbacks... Unfortunately, I was UNsuccessful. Maybe I'm overlooking something, but it seems to me that postbacks are absolutely required for the image preview, and simple callback (or partial postback) wouldn't work... no matter how hard I'd tried.
After doing some research on the Internets (which showed that a lot of people are strugling with an image upload preview), I had to go back to the techniques described in my earlier post... though this time I took one extra step and had developed a user control.

You can download the solution (VS2005) here.

In ImageUploadManager.ascx I have added FileUpload control:


<input id="imgUpload" type="file" onchange="ExecuteFileUpload();" runat="server"
style="width: 100%" />


DIV to hold preview Images:


<div id="fileList" style="text-align: left; position: relative;">
</div>


and a hidden frame to make postback for uploading images in the background:


<div id="fileUpload" style="display: none;">
<iframe src="" id="hiddenFrame" name="hiddenFrame" style="width: 0px; height: 0px"></iframe>
</div


OnChange event attached to the FileUpload control fires ExecuteFileUpload():


function ExecuteFileUpload()
{
submitUpload("hiddenFrame");
document.getElementById("upload_progress").style.display = "block";
}


The above function calls submitUpload(iFrameId), which is dynamicaly generated by the code behind during Page_Load execution:



protected void WriteJavascript()
{
StringBuilder sb = new StringBuilder();
sb.Append("function submitUpload(frameName)");
sb.Append("{");
sb.Append(String.Format("var hform = parent.document.getElementById('{0}');", TargetFormID)); //get Traget Form id from Control Parameters
sb.Append(String.Format("hform.action = \"{0}?upload=true&uid=\"+counter;", Request.CurrentExecutionFilePath));
sb.Append("hform.target = frameName;");
sb.Append("hform.submit();");
sb.Append("hform.action = document.URL;");
sb.Append("hform.target = \"_self\";");
sb.Append("}");
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "submitUpload", sb.ToString(), true);
}


This is the "engine" of the hidden postback. This function reassigns form target to the hidden iFrame, sets action and executes the postback. Note the hform.action assignment, as it sets upload query string to true and assigns uid to the current picture count. We will need that information during the Page_Load processing in the code behind, to distinguish UPLOAD postback from all others, and assign ID to the image. After form is submitted, form ACTION and TARGET are restored to their default values.

Here's how Page_Load method looks like:



protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Session["ImagesList"] = null;
deleteOldPreviewImagesFromServer(); //clean up upload folder
}

string sbReference = Page.ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context");
string cbScript = String.Empty;
if (!Page.ClientScript.IsClientScriptBlockRegistered("CallServer"))
{
cbScript = @" function CallServer(arg,context) { " + sbReference + "}";
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "CallServer", cbScript, true);
}
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "ImageWidth", String.Format("var ImageWidth={0};", imgWidth), true);
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "HdnControlID", String.Format("var hdnControlId=\"{0}\";", hdnSavedImage.ClientID), true);
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "LblMessageID", String.Format("var lblMessageId=\"{0}\";", lblMessage.ClientID), true);


btnSave.PostBackUrl = Request.CurrentExecutionFilePath + String.Format("?save=true&uid={0}", BatchID);
WriteJavascript();
//postback with upload = true should only be fired by imgUpload drop down, when image file is being selected
if (Request.QueryString["upload"] != null && Request.QueryString["upload"] != "")
{
if (Request.QueryString["uid"] != null && Request.QueryString["uid"] != "")
UploadImageInit(Request.QueryString["uid"]); //upload image, and store image "local" URL in hashtable, with index = uid
else
UploadImageInit("Error:Image ID is missing."); //when image ID query string is empty - flag error
}
//postback with save = true should only be fired by btnSave
if (Request.QueryString["save"] != null && Request.QueryString["save"] != "")
{
SaveImagesIntoDB(Request.QueryString["uid"]); //save all active images into DB under uid batch ID
}

}


Here we -
1) register CallBack script (which we use to execute page callback during deletion of the images from the preview pane);
2) register ImageWidth variable (which is used to resize images for the preview pane);
3) assign PostBack URL with the save=true to the Save Button (to distinguish SAVE postback from all others;
4) Register JavaScript for the submitUpload() function;
5) Upload image if "upload" query string is present and set to true;
6) Save all images if "save" query string is present and set to true.

For uploading images - UploadImageInit(string imageID) is fired:


protected void UploadImageInit(string uid)
{
string uploadContext = "Error:Preview Image Init Generic Exception.";
try
{
if (!uid.Contains("Error")) //perform upload only if no errors were flagged earlier
{
Hashtable list; //hashtable to store Images' paths
if (Session["ImagesList"] != null)
{
list = (Hashtable)Session["ImagesList"];
}
else
{
list = new Hashtable();
}
uploadContext = DoFileUpload(); //do image upload
if (uploadContext.Contains("Success")) //on success
{
string path = uploadContext;//;//parse image path from the string returned by upload
list.Add(uid, path); //store image path for future reference
Session["ImagesList"] = list; //save list
}
}
else //error was flagged earlier in processing
{
uploadContext = uid; //reassign returned error message -format should be "Error:Message"
}
}
catch (Exception ex)
{
uploadContext = String.Format("Error:{0}", ex.Message);
}
//Call javascript function in the parent page (since this upload was posted in the child iFrame control)
//Pass path to the freshly uploaded image on the server
Response.Write("<script type='text/javascript' language='javascript'>");
Response.Write(String.Format("parent.FinishImagePreview('{0}');", uploadContext));
Response.Write("</script>");

}


Here we upload image on the server and save image path in the hash table, which is stored in the Session variable for the reference between the postbacks. Hash table is used to keep reference to all pictures currently uploaded in the Preview Pane. When Picture is deleted from the preview pane it is also is deleted from the hash table. Image count from the client is a key value in the hash table.

DoFileUpload() method uploads file posted in the imgUpload control and returns a string in the following format:
Status:Path_To_Image:Image_Height:Image_Width
Satus can be "Success" or "Error";
Path_To_Image is an URL that points to the image file on the server, which is used to preview image by the client. (Image also can be saved directly into the database, and instead of using URL to the physical file on the server, URL of the PictureHandler with the picture ID can used as the source to the image [Picturehandler.ashx?id=12]);

As you can see - at the bottom of UploadImageInit() another javascript function is being fired - FinishImagePreview() (since uploading is being performed in the child frame, we can call javascript in the parent page by envoking Response.Write).


function FinishImagePreview(rValue) {
document.getElementById(lblMessageId).style.display = "none";
// The new path will contain the path of the image on the server
var rArray = new Array();
rArray = rValue.split(":");
if (rArray[0] == "Error") {
alert(rArray[1]);
document.getElementById("upload_progress").style.display = "none";
} else {
CreateNestedElements(rArray[1], rArray[2], rArray[3]);
}
}


rValue contains result string from the DoFileUpload() method. In the above method we split result string and depending on the returned status - either generate image in the preview panel, or alert about an error.

When images are being previewed - the Upload Pane should look similar to this:





As you can see - images can be removed from the Preview. To achieve that I used page callbacks. If you look in the CreateNestedElements() function - you'll see that DeleteItem() function is being assigned to the onlick event for the delete icon:

deleteButton.onclick = DeleteItem;

DeleteItem() fires CallServer function (generated during execution of Page_Load), which removes image parameters from the Hashtable in the code behind and deletes image DIV in the client:

function DeleteItem(e) {

document.getElementById("upload_progress").style.display = "block";
var evt = e || window.event;
var evtTarget = evt.target || evt.srcElement;
CallServer(evtTarget.id, '');


// IE
if (evtTarget.parentElement) {
var childDiv = evtTarget.parentElement;
childDiv.parentElement.removeChild(childDiv);
}

// FIREFOX
else if (evtTarget.parentNode) {
var childDiv = evtTarget.parentNode;
childDiv.parentNode.removeChild(childDiv);
}
}


When CallServer is called - the following method in the code behind is being executed:

public void RaiseCallbackEvent(string eventArgument) //fired by client on Image Deletion - CallServer
{
if (!String.IsNullOrEmpty(eventArgument))
{
returnValue = eventArgument;
}

}


That method is a member of the ICallbackEventHandler interface. There's one more method required for the interface implementation to work:

public string GetCallbackResult() //fired by client on Image Deletion
{
string result = "";
try
{
Hashtable list;
if (Session["ImagesList"] != null)
{
list = (Hashtable)Session["ImagesList"];
}
else
{
list = new Hashtable();
}
DeleteImageFile(((string)list[returnValue]).Split(':')[1]);
list.Remove(returnValue);
result = "Success:Deleted";
Session["ImagesList"] = list;
}
catch (Exception ex)
{
result = String.Format("Error:{0}", ex.Message);
}

return result; //result is returned to the client to ReceiveServerData(rValue)
}


This is where the actual work is happening. This methods removes image reference from the hash table and returns result string to the client (in the similar format described above), by calling ReceiveServerData():

function ReceiveServerData(rValue)
{
document.getElementById("upload_progress").style.display = "none";
var rArray = new Array();
rArray = rValue.split(":");
if (rArray[0] == "Error") {
alert(rArray[1]);
}
}


Save button causes the postback with the query string "save=true" to occur. When saving sequence is initiated in the code behind, Hash table with the references to all uploaded images is used to SAVE images to Database.

Callbacks can also be handled by ASP.NET AJAX ScriptManager. Though you would have to put it outside of the control, and modify control to call AJAX WebService for image deletion.

Control has several parameters:
TargetFormID - ID of the form containing Image Upload Control;
MaxImageSize - Maximum size of the image file;
TempUploadPath - Name of the temp upload folder on the server;
BatchID - ID of the control itself;
IconWidth - Width of the preview image (which is also a height);

Don't forget to include CSS file:

<link href="../StyleSheets/Global.css" rel="stylesheet" type="text/css" />


Feel free to modify and share. Let me know if you have ideas for improvements or any comments. Hope this helps.

Tuesday, February 3, 2009

ASP.NET 2.0 text field value is not being passed to code behind

Whenever ASP.NET 2.0 TextBox component is set to Disabled or ReadOnly through the VS2005 designer, and then modified by client JavaScript - input value is not being persisted on the postback.
To work around this - set Disabled or ReadOnly from the client JavaScript when the page loads.
e.g.

function ExecuteOnLoad()
{
document.getElementById("txtInput").readOnly = true;
document.getElementById("txtOutput").disabled = true;
}

Viewstate is lost for dynamically (javascript/AJAX) generated dropdown box

I had a page with the dynamically (AJAX) populated dropdown box, which was failing to persist the state whenever ASP.NET postback was executed. To work around the problem - I created a hidden field, and attached SelectedIndexChange event to the dropdown, which populated hidden field with the selected value from the dropdown...
Simple enough!

Just make sure EnableEventValidation is set to false, otherwise the following error is being thrown:

Invalid postback or callback argument. Event validation is enabled using in configuration or <%@ Page EnableEventValidation="true" %> in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.