Mehfuz's WebLog

Live crazy, think different!

Sponsors

News

Passionate about cutting edge technologies and facinated by the modern web and phone revolution.Currently working at Telerik Corporation, the leading .net component vendor.
Follow me


Articles


Projects

September 2008 - Posts

Asp.net MVC more Form post scenarios and Ajax

In the flickr explorer app made with Asp.net MVC and Athena, I was trying out some form post scenarios. In this post, I will add few things regarding MVC form post and Ajax that is common to FlickrXplorer but can be used in general.

Now, Asp.net MVC has some new Ajax features, one of this is the Ajax form post. With this, you can easily make your web app actions Ajax enabled. The syntax is pretty simple.

<% using (Ajax.Form("AddComment", "Comment", new AjaxOptions { UpdateTargetId = "cmContainer", 
           OnBegin = @"function(sender, args) {
                $get('caWait').style.display = 'block';
           }", 
           OnSuccess = @"function(sender, args) {
             $get('caWait').style.display = 'none';
           }", 
          }))
                            
       { %>

...
...

<% } %>

The piece of snippet is taken from FlickrXplorer : CommentsControl.ascx where I used MVC Ajax to do comment posts. Here you can see that I can pass in the UpdateTargetId , where the output of ContentResult or  if it is ViewResult, it will contain the rendered html for it. Additionally, there is OnBegin and OnSuccess events where I can do things like show or hide the wait panel and any other things I might like to do.

On the client code , probably in your default.master you need to have the following references to make things work.

<script type="text/javascript" src="<%= Page.ResolveClientUrl("~/Content/MicrosoftAjax.js") %>" ></script>
<script type="text/javascript" src="<%= Page.ResolveClientUrl("~/Content/MicrosoftMvcAjax.js") %>" ></script>

Now, this is one way of doing things out. This basically injects the Ajax script in onsubmit of the html form where the actual magic happens. Now, this is not the only way, you might need to do custom tasks before the submission takes place, like check if the user has written something before pressing the submit button or selected his country dropdown before pressing checkout in an air-ticket inventory system. For this kind of issue,  here is another cool way that it can be done.

function doAjaxSubmit(form, e, waitPanelId, targetToUpdate, methodName) {
    
    var isValid = true;
    
    if (typeof methodName != 'undefined')
    {
        var validate = Function.createDelegate(this, methodName)    
        isValid = validate(form, null);
    }

    if (isValid) {

        if (e == null) return false;
        
        Sys.Mvc.AsyncForm.handleSubmit(form, new Sys.UI.DomEvent(e),
        {
            insertionMode: Sys.Mvc.InsertionMode.replace,
            updateTargetId: targetToUpdate,
            onBegin: Function.createDelegate(form, function(sender, args) {
                $get(waitPanelId).style.display = 'block';
            }),
            onSuccess:
            Function.createDelegate(form, function(sender, args) {
                $get(waitPanelId).style.display = 'none';
            })
        });

    }
    else {
        return false;
    }
}

So, I have written a tiny JS routine that is a similar script that Ajax.Form injects in onsubmit event of html form but with few changes. As, we can see that I need to pass the form instance (this), event (in this case submit), the target where the update will be pushed in and a delegate method if provided it will process the submit only if it passes the validation written by the implementer.

In the FlickrXplorer : CommentsControl.ascx i have modified previous the html block like

<% 
IDictionary<string, object> formParams = new Dictionary<string, object>();
formParams.Add("onsubmit", @"return doAjaxSubmit(this, event, 'caWait', 'cmContainer', function(sender, args) 
{
    return validateRequiredText('errComment', 'txtComment');
});");

using (Html.Form("Comment", "AddComment", Microsoft.Web.Mvc.FormMethod.Post, formParams))
                    
{ %>

<%= Html.Hidden("photoId", photoId) %>
...
...

<% 
} %>

In this case, I have converted the Ajax.Form to Html.Form, where I have hooked the onsubmit with doAjaxSubmit routine with few parameters. Inside the call back method, I have written the required logic for validating required field. So, if people lefts the comment textarea intentionally blank and tries to do a post he might see.

image 

Now, moving on to the next section, I would like to like see my pager do MVC form post but I would like to see a continue of parents parameters. Let's take the use case.

image

In other words, I need to delegate form action from parent to child. There are plenty of ways to do it. In my way, I have created a HtmlHelper extension method that prepares my child form with query params that come from the parent request.

public static string RenderRequiredFormPostElements(this HtmlHelper htmlHelper, string paramToExclude)
{
    string url = HttpContext.Current.Request.Path;

    StringBuilder builder = new StringBuilder();
    NameValueCollection queries = HttpContext.Current.Request.QueryString;

    foreach (string name in queries.AllKeys)
    {
        if (string.Compare(name, paramToExclude, true) == 0)
        {
            //skip
        }
        else
        {
            builder.Append(htmlHelper.Hidden(name, new {value = queries[name]}));
        }
    }
    return builder.ToString();
}

Here it renders html hidden elements based the current url, also I can pass in a particular query param (in this case "page") which I don't want to be processed. Finally, in the FlickrXplorer : Pager.ascx i have added the following lines

<form name="pagerForm" method="GET" action="<%= HttpContext.Current.Request.Path %>">

<%= Html.Hidden("page") %>         
<% = Html.RenderRequiredFormPostElements("page") %>             

...
...

Here, to include that  pager action is bound to the current url , which is in turn bound to an action in the controller class. Therefore, in this case Request.Path points to an action url /Photo/{action}  with paging value. Also, the possible action methods for which paging  value will be supplied need to handle the case if there is no paging.

That's all for now. All the example are shown here, ties to Asp.net MVC 5. You can find codes running at www.codeplex.com/flickrXplorer , please go to the source tab to grab the nightly build.

Enjoy !!!

kick it on DotNetKicks.com
Deploy, test and pack your code - Part 2 Using MsBuild

In part one of this packing series I have shown, how you can automate your build process, test and optionally mock by using the popular .net deployment tool NAnt. If you don't know what I am talking about and just landed here by help of search engine. Let me put the link below again.

http://weblogs.asp.net/mehfuzh/archive/2008/08/30/deploy-test-and-pack-your-code-part-1-using-nant.aspx

In this post, I will do a replay to mostly what I have talked in my last post but with MsBuild. Let's start with building the code. If you have a chance to see the above link you must have seen that I have to do strict maps of source files under the csc task as well as the references to make things work. Now, searching the NAnt project I have found that it has a cool solution task, which even let you define your own output path for libraries but unfortunately it does not work with .net 3.5 and Visual studio 2008.

Typically , the solution task is defined under NAnt.VSNetTasks.dll also depends on the following assemblies

image

To use the task inside build script you need to do something like

<loadtasks assembly="ThirdParty\Nant\NAnt.VSNetTasks.dll" />
<solution solutionfile="LinqToFlickr.sln" configuration="release" outputdir="Bin" />

But , it ends up with "Solution format not supported" exception for VS 2008 solution. We can obviously use the csc task instead and someday it will support the solution task but you can do things with MsBuild more easily. Let's see the snippet below

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <Target Name="Build">
    <MSBuild Projects="LinqToFlickr.sln"
          Properties="Configuration=Release"  />
  </Target>
  
</Project>

I have put the snippet under Linq.flickr.Targets. Now, if your solution has multiple projects and you want the output to go to a specific folder you can easily do that using the project's "property page".  The advantage here is that, if a developer adds a new file, the build guy don't need to worry about syncing them up and thus if you have a nice set of rules in your team, it can be far more time saving than strict mapping.

The next step is to test and deploy the library. I have defined another .target file for holding the named variables , which I will be using later. I named it include.Targets.

For , those who are curious, declaring property in MsBuild looks like

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <TaskDirectory>Tasks</TaskDirectory>
    <NUnit>ThirdParty\NUnit\nunit-console.exe</NUnit>
  </PropertyGroup>

  <UsingTask AssemblyFile="$(TaskDirectory)\MSBuild.Community.Tasks.dll"
           TaskName="MSBuild.Community.Tasks.Zip"
               />
</Project>

As you can see it is possible to reference the properties later on (in this file or other files that includes it) by $(...) format. UsingTask  is the MsBuild way of loadtasks in NAnt which also mimics with the namespace directives in C#.

To wrap up everything, I have created a master.proj file which is the entry point. it includes the targets and does the test and additionally zip things up. You can open up the master.proj in VS 2008. This will  give you the advantage of intellisense, but here to mention that the intellisense wont work nicely for custom made tasks. Inside the project node of master.proj you can specify default target like NAnt and can make other targets depend on it. So, even if the target is in one of files included, it will still fire up everything else before playing the final target.

<Project DefaultTargets="Deploy" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
....
</Project>

We have built the project, its time to copy them in a folder to do something on them before doing the final packing. I have created a tiny Task called BatchCopy (I really missed one in the community tasks). The usage is pretty simple and it works pretty nice in my  project scope

<BatchCopy source="$(SourceDir)\Bin\Release" DestinationFolder="$(BinDir)" ExtensionToExclude=".pdb;" />
<BatchCopy source="$(TestDir)\Bin\Release" DestinationFolder="$(BinDir)" ExtensionToExclude=".pdb;" />

As you can see, it requires a source and destination path and there is another property where I can specify the extensions which I want to skip separated by commas.

Till this line, I have built the project , copied assemblies to a folder, next cool thing is to test them out. Testing definitely to be done by NUnit. In Athena(Flickr API), I have used Typemock to fake request, responses, authentication, etc out so that I can test the whole API while I am on a plane to PDC 08 :-).

In the previous post, I have mentioned Typemock 5 made auto deploy possible for open source projects. Let's get that going. 

Step one is to include three tasks

<UsingTask TaskName="TypeMock.MSBuild.TypeMockRegister" AssemblyFile="$(TypeMockLocation)\TypeMock.MSBuild.dll" />
<UsingTask TaskName="TypeMock.MSBuild.TypeMockStart" AssemblyFile="$(TypeMockLocation)\TypeMock.MSBuild.dll" />
<UsingTask TaskName="TypeMock.MSBuild.TypeMockStop" AssemblyFile="$(TypeMockLocation)\TypeMock.MSBuild.dll" />

TypemockLocation is not the application folder rather where I have copied necessary dlls to make the auto deploy possible. Basically to run it as a standalone your deployment folder should have the following dlls in place copied from the Typemock installation folder.

image

Once everything is in place, the steps are the same that I have mentioned for NAnt. Only, few syntax that you need to watch out with wide eyes.

<TypeMockRegister Company ="Open Source" License="Get one for you" AutoDeploy="True"/>

<TypeMockStart/>
      <Exec ContinueOnError="false" Command="$(NUnit) $(BinDir)\Linq.Flickr.Test.dll"/>
<TypeMockStop/>

Don't forget to add the ContinueOnError="false" that halts the script as soon as there is an error. Once, things are right running the build script will register auto deploy and run over your test project giving you the results.

Lastly, moving to master.proj, I just wanted to zip everything up with the proper content.

<CreateItem Include="$(BinDir)\**\Linq.Flickr.dll;$(BinDir)\**\LinqExtender.dll;readme.txt;" >
      <Output ItemName="FilesToZip" TaskParameter="Include"/>
</CreateItem>

<Zip Files="@(FilesToZip)" Flatten="true" 
                ZipFileName="$(BinDir)\Linq.Flickr.Lib.zip" />

The CreateItem task lets you prepare an item collection based on the parameters you passed in the Include/Exclude attribute and finally outputs the list to a variable that you can use later on (in this case, it is used with the Files attribute of the Zip task).

This is just a simple way of getting things around with MsBuild. To spice a bit more, you can wrap the msbuild command in a batch file, which requires the following two lines

@ECHO ON
C:\Windows\Microsoft.NET\Framework\v3.5\msbuild.exe Master.proj /m:2 /fileLogge

fileLogger will create an output file (msbuild.log) after the build that contains the detail of execution. /m defines that msbuild will run in two process in parallel, this option is pretty good for large solution (for ex, Telerik has plenty of projects in a solution and I found building with msbuild much faster than using the VS IDE), of course having quad core processor with /m:4 will give more boost than in two cores . In my other project which is LinqExtender, I created a script that download source code and setup working base with a click of a button. Therefore, those who check in/out things in codeplex or any other source control environment could find this task useful.

  <TFSGet
            Server="tfs05.codeplex.com"
            Port="443"
            Secured="true"
            Repository="$/LinqExtender/somthing"
            UseUICredentials="false/true"
            Username="$(User)"
            Password="$(Pass)"
            Domain ="snd"
            LocalPath="local path to host the source"
            UnBind = "true/false"
            />

Here, UseUICredential = true, will bring up the TFS login prompt every time, so if you don't want to type in user/pass every time then just leave as it is and populate the username and password parameter with proper values.  Unbind  will remove the source control binding after downloading , if set to true.

Last but not the least, there is another task that I want to share with you is called XmlFindReplace, this is useful if you are automating VSI package creation  which I did in my LinqExtender project (I hate doing this manually every time for deployment :-))  or want to create a starter project with predefined template.

<XmlFindReplace
      FilePath=".\Template\project.xml"
      Ns ="http://schemas.microsoft.com/developer/msbuild/2003"
      Attribute = "Include"
      Element ="Reference"
      Text ="#ASSEMBLY#"
      ReplaceText="LinqExtender, Version=1.4.0.0, Culture=neutral, processorArchitecture=MSIL"
      DestinationFile="$(TempDir)\LINQProvider.csproj"
/>

Here you can do two sets of find and replace. Either replace the attribute value of an element which requires both the Element and its Attribute or just replace the element text in which case you need to provide only the Element name.

So far, you can see that it is possible to do same things with Msbuild that is possible with NAnt, but MsBuild gives more integration to VS environment which sometimes give more power over how the build goes. Earlier, in a post I have shown that at the end of each project file there is a "AfterBuild"  and "BeforeBuild" target where you can put your own script to do some special tasks. Also, Msbuild comes as part of .net framework so that you don't ever need to bother about the script runtime and distributes.

Hope all these info are useful for you to get going, I have added the tasks with test classes so that you can play, use or extend it as you like.

kick it on DotNetKicks.com
Posted: Sep 07 2008, 12:41 AM by mehfuzh | with 4 comment(s) |
Filed under: , , ,
More Posts