June 2010 - Posts

With the release of Iron Ruby we are no more tide up with the xml based build script like NAnt and MSBuild, we can now use Rake with Iron Ruby to build our .NET based projects. In this post, I will show you a very basic build script in rake that will integrate StyleCop, Simian, FxCop, MSpec and NCover. I will use the same fund transfer project that I used in my previous post.

Before moving to the build script, let me give a brief description of the above tools:

  1. StyleCop: It works on source code and ensures that all of rules that you have defined earlier has been meet. These rules can include ode structure, naming Convention, documentation rules etc, you can even create your own custom rules if you want. It has been recently open sourced by Microsoft and can be download from http://stylecop.codeplex.com.
  2. Simian is another excellent tool which also works on source code. It detects the duplicate codes among the given source code files. Though it is a commercial product but it is free to use in non commercial or open source projects. It supports a lot of languages and you can download it from http://www.redhillconsulting.com.au/products/simian.
  3. FxCop is one of the tool that is available from the early days of .NET, similar to StyleCop but it enforce the rules on compiled outputs rather than source codes. The latest version of FxCop is included in the Windows 7 SDK which you can download from the Microsoft Download Center. In this post, I will be using the last standalone version 1.36.
  4. MSpec is my BDD Style Test Framework of choice. Currently there is no official binary version but you can download it and compile it yourself.
  5. NCover is my another favorite tool which reports the code coverage. Usually it is used with the test runner to capture the coverage. In this post I will be using the community edition of NCover which is free. You can download it from http://www.ncover.com/download/current.

Now we know the basics of the these tools, lets define the steps, since both StyleCop and Simian works on source codes we will invoke these before compiling our source codes and rest of the steps after the compilation:

  1. Run StyleCop
  2. Run Simian
  3. Compile
  4. Run FxCop
  5. Run Test And Coverage.
  6. Copy the build artifacts in the Drop location.

This is the skeleton of the rake script for the above steps:

task :style_cop do
	puts "Running StyleCop"
end

task :simian do
	puts "Running Simian"
end

task :compile do
	puts "Compiling projects"
end

task :fx_cop do
	puts "Running FxCop"
end

task :test_and_coverage do
	puts "Running Test and capturing the code coverage"
end

task :drop do
	puts "Preparing the build artifacts"
end

task :default => [:style_cop, :simian, :compile, :fx_cop, :test_and_coverage, :drop] do
	puts "Done"
end

Consider task as MSBuild Target and it has an associated name (ruby symbol) denoted as :name which is same as MSBuild Target name. To make a task depended on other tasks you have to specify the task names in square brackets like the last task of in above script. To invoke one or more specific tasks you have to pass the task name as argument, for multiple tasks use space as separator. For example:

rake simian fx_cop # The rake command by default executes rakefile.rb
will show output:
Running Simian
Running FxCop

If none of the task is passed, it will execute the default task.

Now, lets add the codes in the above skeleton, but before that lets see the directory structure of the Fund Transfer Project:

RakeFS

Though we have six main tasks but there few more things that we have to add. First the initialization

MSBUILD = File.join(ENV["windir"] || ENV["WINDIR"], "Microsoft.NET", "Framework", "v3.5", "msbuild.exe")

CONFIG = ENV["config"] || ENV["CONFIG"] || "Debug"

CURRENT_PATH = File.dirname(__FILE__)
ARTIFACT_PATH = File.join(CURRENT_PATH, "Artifacts")
REPORT_PATH = File.join(ARTIFACT_PATH, "Reports")
REFERENCES_PATH = File.join(CURRENT_PATH, "References")
TOOLS_PATH = File.join(CURRENT_PATH, "Tools")
TEST_SUFFIX = "Specs"

projects = []
app_projects = []
app_files = []
app_dependency_files = []
test_projects = []
test_files = []
test_dependency_files = []
referenced_files = []

desc "Initilizing build"
task :init do

	# This will create the Artifacts directory if it does not exist
	FileUtils.mkdir_p ARTIFACT_PATH

	# Delete the existing files/folders of the Artifacts directory
	Dir.foreach(ARTIFACT_PATH) do | entry |
		FileUtils.remove_entry(File.join(ARTIFACT_PATH, entry)) if (entry != ".") && (entry != "..")
	end

	# Create the Report folder
	FileUtils.mkdir_p REPORT_PATH

	# Get all the CSharp Project files that exists under the current path
	projects = Dir.glob(File.join(CURRENT_PATH, "/**/*.csproj"))

	# Now we have to indentify the Test/Spec project and the regular project
	projects.each do | project |

		project_name = File.basename(project, ".csproj")

		# If Project has the special suffix we will treat it as Test/Spec Project
		# otherwise it is a regular project
		if /#{TEST_SUFFIX}$/.match(project_name)
			test_projects << project
		elsif
			app_projects << project
		end
	end

end

In the above we are first declaring few constants(please note that in Ruby Constants are started with capital letters) and few variables which we are going to use in the tasks. The File.Join is same as .NET Path.Combine. The ENV[YOUR_KEY] is used get the value of Environment variables. In the init task we are making sure the Artifacts and its subfolder Reports exists, next we are scanning all the projects and storing it in the two array’s for later usages. By default, the script assumes that the MSpec projects ends with “Specs”. Next, we will add one more helper task which will clear the build outputs:

desc "Cleaning up outputs"
task :clean do

	# Iterate all the project and pass to MSBuild for cleaning up the build outputs
	projects.each do | project |
		sh "\"#{MSBUILD}\" \"#{project}\" /p:Configuration=#{CONFIG} /t:Clean /tv:3.5"
	end

end

The sh stands for shell, we are using it to execute the MSBuild.exe and passing the required parameters.

Now, lets modify the default task’s dependencies so that the above two tasks are executed prior the others:

desc "Default"
task :default => [:init, :clean, :style_cop, :simian, :compile, :fx_cop, :test_and_coverage, :drop] do

	puts "Build Completed."

end

Among the tasks most of the tasks are invoking external processes, only the style_cop task requires bit of interaction with the clr types. Although StyleCop comes with MSBuild task but as we are not running in MSBuild environment, we cannot use it directly, instead we have to call the StyleCop directly from our rake script and this is the true power of Iron Ruby, we can easily call any clr type and vice versa. The following shows the code which runs the StyleCop analysis:

desc "Running StyleCop"
task :style_cop do

	# We will need the reference of StyleCop
	require File.join(TOOLS_PATH, "stylecop", "Microsoft.StyleCop.dll")

	# Create a List<StyleCopProject> to store the regular projects
	style_cop_projects = System::Collections::Generic::List[Microsoft::StyleCop::CodeProject].new
	
	# Build the File path of StyleCop Report
	report_file = File.join(REPORT_PATH, "StyleCop.xml")

	# Create a StyleCop console to apply the StyleCop rules
	style_cop_console = Microsoft::StyleCop::StyleCopConsole.new(nil, false, report_file, nil, true)
	
	# Create a StyleCop Configuration
	style_cop_configuration = Microsoft::StyleCop::Configuration.new(nil)

	# We will only invoke StyleCop for the regular project, not for the Test/Specs.
	app_projects.each do | project |

		style_cop_project = Microsoft::StyleCop::CodeProject.new(project.hash, project, style_cop_configuration)
		
		# Add all the .cs files that resides in this project
		source_files = Dir.glob(File.join(File.dirname(project), "/**/*.cs"))

		source_files.each do | source_file |
			style_cop_console.core.environment.add_source_code(style_cop_project, source_file, nil)
		end

		style_cop_projects.add(style_cop_project)
	end

	# Start applying the rules
	style_cop_console.start(style_cop_projects, true)
	
	# Now release all the associated resources of StyleCop
	style_cop_console.dispose()

end

The next two tasks are simple external program execution, it just prepares the required parameters and invokes the program:

desc "Running Simian"
task :simian do

	simian_path = File.join(TOOLS_PATH, "simian")
	simian_exe = File.join(simian_path, "simian-2.2.24.exe")
	simian_report = File.join(REPORT_PATH, "Simian.xml")

	app_projects_paths = app_projects.map { | project | "\"" + File.dirname(project) + "\""}.join(" ")

	# Copy the xsl file in the report folder so that we can view the xml as html
	FileUtils.copy(File.join(simian_path, "Simian.xsl"), REPORT_PATH)

	sh "\"#{simian_exe}\" #{app_projects_paths} -formatter=xml:\"#{simian_report}\" -failOnDuplication- -reportDuplicateText+ -includes=\"**/*.cs\""

end

desc "Compiling source"
task :compile do

	projects.each do | project |
		sh "\"#{MSBUILD}\" \"#{project}\" /p:Configuration=#{CONFIG} /t:Build /tv:3.5"
	end

end

Now we add one last helper task, this will ensure that all the project outputs and referenced components are copied into the build Artifacts folder, so that we can use the folder as Working Directory to run the FxCop and MSpec specifications.

desc "Copy compiled files"
task :copy_files do
	
	app_projects.each do | project |

		project_directory = File.dirname(project)
		project_name = File.basename(project, ".csproj")

		project_outputs = Dir.glob(File.join(File.join(project_directory, "bin", CONFIG), "/**/*.*"))

		project_outputs.each do | file |
		
			base_name = File.basename(file)

			if File.basename(base_name, File.extname(file)).eql?(project_name)
				app_files << base_name
			else
				app_dependency_files << base_name
			end
			
			FileUtils.copy(file, ARTIFACT_PATH);

		end

	end

	test_projects.each do | project |

		project_directory = File.dirname(project)
		project_name = File.basename(project, ".csproj")

		project_outputs = Dir.glob(File.join(File.join(project_directory, "bin", CONFIG), "/**/*.*"))

		project_outputs.each do | file |

			base_name = File.basename(file)

			if File.basename(base_name, File.extname(file)).eql?(project_name)
				test_files << base_name
			else
				test_dependency_files << base_name if !app_files.include?(base_name) && !app_dependency_files.include?(base_name)
			end

			FileUtils.copy(file, ARTIFACT_PATH);

		end

	end

	Dir.glob(File.join(REFERENCES_PATH, "/**/*.*")).each do | file |

		referenced_files << File.basename(file)

		FileUtils.copy(file, ARTIFACT_PATH);

	end

end

Depending upon the configuration (usually Debug/Release) we are copying the build outputs from the bin directory of each project and putting it into the Artifacts folder, when copying we are also populating few file list variables so that we can later use it in the FxCop and MSpec tasks. This tasks will be invoked after the compiling the projects. So we will again modify the dependencies of the default task.

desc "Default"
task :default => [:init, :clean, :style_cop, :simian, :compile, :copy_files, :fx_cop, :test_and_coverage, :drop] do

	puts "Build Completed."

end

The next two tasks are also invoking the external processes and building the required parameters:

desc "Running FxCop"
task :fx_cop do

	fxcop_path = File.join(TOOLS_PATH, "fxcop")
	fxcop_exe = File.join(fxcop_path, "FxCopCmd.exe")
	fxcop_report = File.join(REPORT_PATH, "FxCop.xml")

	dlls = "/f:" + app_files.select{ |file| File.extname(file).eql?(".dll")}.map { | dll | "\"" + File.join(ARTIFACT_PATH, dll) + "\""}.join(" /f:")

	FileUtils.copy(File.join(fxcop_path, "Xml", "FxCopReport.xsl"), REPORT_PATH)
	
	sh "\"#{fxcop_exe}\" #{dlls} /d:\"#{ARTIFACT_PATH}\" /o:\"#{fxcop_report}\" /oxsl:\"FxCopReport.xsl\" /to:0 /fo /gac /igc /q"

end

desc "Running tests"
task :test_and_coverage do

	test_runner_exe = File.join(TOOLS_PATH, "mspec", "mspec.exe")

	app_dlls = app_files.select{ |file| File.extname(file).eql?(".dll")}.map { | dll | File.basename(File.basename(dll), File.extname(dll)) }.join(";")
	test_dlls = test_files.select{ |file| File.extname(file).eql?(".dll")}.map { | dll | File.join(ARTIFACT_PATH, dll) }.join(" ")

	test_runner_argument = "\"#{test_runner_exe}\" --html \"#{REPORT_PATH}\" \"#{test_dlls}\""

	exclude_attributes = "System.Runtime.CompilerServices.CompilerGeneratedAttribute;System.CodeDom.Compiler.GeneratedCodeAttribute;System.Diagnostics.DebuggerNonUserCodeAttribute"

	ncover_coverage = File.join(REPORT_PATH, "NCover.Console.xml")
	ncover_path = File.join(TOOLS_PATH, "ncover")
	ncover_console_exe = File.join(ncover_path, "NCover.Console.exe")
	ncover_console_argument = "#{test_runner_argument} //a \"#{app_dlls}\" //x \"#{ncover_coverage}\" //ea \"#{exclude_attributes}\" //w \"#{ARTIFACT_PATH}\" //q"

	FileUtils.copy(File.join(ncover_path, "Coverage.xsl"), REPORT_PATH)

	sh "RegSvr32 \"#{File.join(ncover_path, "CoverLib.dll")}\" /s"

	sh "\"#{ncover_console_exe}\" #{ncover_console_argument}"

	sh "RegSvr32 \"#{File.join(ncover_path, "CoverLib.dll")}\" -u /s"
	
	ncover_explorer_exe = File.join(ncover_path, "NCoverExplorer.Console.exe")
	ncover_report = File.join(REPORT_PATH, "NCoverExplorer.Console.xml")

	FileUtils.copy(File.join(ncover_path, "CoverageReport.xsl"), REPORT_PATH)

	sh "\"#{ncover_explorer_exe}\" \"#{ncover_coverage}\" /r:ModuleClassFunctionSummary /x:\"#{ncover_report}\" /q"

end

And in the last task we will do some cleanup so that the artifacts directory only contains the application dll/pdb/xml docs and all the xml/html reports of the above tools.

desc "Preparing drop"
task :drop do

	app_dependency_files.concat(test_files).concat(test_dependency_files).concat(referenced_files).uniq().each do | file |
		FileUtils.remove_file(File.join(ARTIFACT_PATH, file), true)
	end
	
end

If you open the Artifacts and the Reports folder after running the rake scripts, you will see:

artifacts

As mentioned that it is a very basic rake example which you can use for building relatively simple applications as it does not have any customization support (though you can always modify the source code). But the good news is there is already rake task library for the .NET applications called Albacore which I highly recommend you to check.

That’s it for today. You can download the complete source code of this post from the following link.

Download: RakeIntegration.zip

 

Shout it

[Note: This is not a continuation of my previous post, treat it as an experiment out in the wild. ]

Lets consider the following class, a fictitious Fund Transfer Service:

public class FundTransferService : IFundTransferService
{
	private readonly ICurrencyConversionService currencyConversionService;

	public FundTransferService(ICurrencyConversionService currencyConversionService)
	{
		this.currencyConversionService = currencyConversionService;
	}

	public void Transfer(Account fromAccount, Account toAccount, decimal amount)
	{
		decimal conversionRate = currencyConversionService.GetConversionRate(fromAccount.Currency, toAccount.Currency);
		decimal convertedAmount = conversionRate * amount;

		fromAccount.Withdraw(amount);
		toAccount.Deposit(convertedAmount);
	}
}

public class Account
{
    public Account(string currency, decimal balance)
    {
        Currency = currency;
        Balance = balance;
    }

    public string Currency { get; private set; }

    public decimal Balance { get; private set; }

    public void Deposit(decimal amount)
    {
        Balance += amount;
    }

    public void Withdraw(decimal amount)
    {
        Balance -= amount;
    }
}

We can write the spec with MSpec + Moq like the following:

public class When_fund_is_transferred
{
	const decimal ConversionRate = 1.029m;
	const decimal TransferAmount = 10.0m;
	const decimal InitialBalance = 100.0m;

	static Account fromAccount;
	static Account toAccount;
	static FundTransferService fundTransferService;

	Establish context = () =>
	{
		fromAccount = new Account("USD", InitialBalance);
		toAccount = new Account("CAD", InitialBalance);

		var currencyConvertionService = new Moq.Mock<ICurrencyConversionService>();
		currencyConvertionService.Setup(ccv => ccv.GetConversionRate(Moq.It.IsAny<string>(), Moq.It.IsAny<string>())).Returns(ConversionRate);

		fundTransferService = new FundTransferService(currencyConvertionService.Object);
	};

	Because of = () => fundTransferService.Transfer(fromAccount, toAccount, TransferAmount);

	It should_decrease_from_account_balance = () => fromAccount.Balance.ShouldBeLessThan(InitialBalance);

	It should_increase_to_account_balance = () => toAccount.Balance.ShouldBeGreaterThan(InitialBalance);
}

and if you run the spec it will give you a nice little output like the following:

When fund is transferred
» should decrease from account balance
» should increase to account balance

2 passed, 0 failed, 0 skipped, took 1.14 seconds (MSpec).

Now, lets see how we can write the exact spec in RSpec.

require File.dirname(__FILE__)  + "/../FundTransfer/bin/Debug/FundTransfer"
require "spec"
require "caricature"

describe "When fund is transferred" do

	Conversion_Rate = 1.029
	Transfer_Amount = 10.0
	Initial_Balance = 100.0

	before(:all) do
	
		@from_account = FundTransfer::Account.new("USD", Initial_Balance)
		@to_account = FundTransfer::Account.new("CAD", Initial_Balance)
	
		currency_conversion_service = Caricature::Isolation.for(FundTransfer::ICurrencyConversionService)
		currency_conversion_service.when_receiving(:get_Conversion_Rate).with(:any, :any).return(Conversion_Rate)

		fund_transfer_service = FundTransfer::FundTransferService.new(currency_conversion_service)
				
		fund_transfer_service.transfer(@from_account, @to_account, Transfer_Amount)
		
	end
	
	it "should decrease from account balance" do
	
		@from_account.balance.should be < Initial_Balance
		
	end
	
	it "should increase to account balance" do
	
		@to_account.balance.should be > Initial_Balance
		
	end
		
end

I think the above code is self explanatory, treat the require(line 1- 4) statements as the add reference of our visual studio projects, we are adding all the required libraries with this statement. Next, the describe which is a RSpec keyword. The before does exactly the same as NUnit's Setup or MsTest’s TestInitialize attribute, but in the above we are using before(:all) which acts as ClassInitialize of MsTest, that means it will be executed only once before all the test methods. In the before(:all) we are first instantiating the from and to accounts, it is same as creating with the full name (including namespace)  like fromAccount = new FundTransfer.Account(.., ..), next, we are creating a mock object of ICurrencyConversionService, check that for creating the mock we are not using the Moq like the MSpec version. This is somewhat an interesting issue of IronRuby or maybe the DLR, it seems that it is not possible to use the lambda expression that most of the mocking tools uses in arrange phase in Iron Ruby, like:

currencyConvertionService.Setup(ccv => ccv.GetConvertionRate(Moq.It.IsAny<string>(), Moq.It.IsAny<string>())).Returns(ConvertionRate);

But the good news is, there is already an excellent mocking tool called Caricature written completely in IronRuby which we can use to mock the .NET classes. May be all the mocking tool providers should give some thought to add the support for the DLR, so that we can use the tool that we are already familiar with. I think the rest of the code is too simple, so I am skipping the explanation.

Now, the last thing, how we are going to run it with RSpec, lets first install the required gems. Open you command prompt and type the following:

igem sources -a http://gems.github.com

This will add the GitHub as gem source.

Next type:
igem install uuidtools caricature rspec

and at last we have to create a batch file so that we can execute it in the Notepad++, create a batch like in the IronRuby bin directory like my previous post and put the following in that batch file:

@echo off
cls
call spec %1 --format specdoc
pause

Next, add a run menu and shortcut in the Notepad++ like my previous post. Now, when we run it, it will show the following output:

When fund is transferred
- should decrease from account balance
- should increase to account balance

Finished in 0.332042 seconds

2 examples, 0 failures
Press any key to continue . . .

You will get the complete code of this post in the following link.

That's it for today.

Download: RSpecIntegration.zip

Shout it

Recently I have decided to learn Ruby and for last few days I am playing with IronRuby. Learning a new thing is always been a fun and when it comes to adorable language like Ruby it becomes more entertaining.

Like any other language, first we have to create the development environment. In order to run IronRuby we have to download the binaries form the IronRuby CodePlex project. IronRuby supports both .NET 2.0 and .NET 4, but .NET 4 is the recommended version, you can download either the installation or the zip file. If you download the zip file make sure you added the bin directory in the environment path variable. Once you are done, open up the command prompt and type :

ir –v

It should print message like:

IronRuby 1.0.0.0 on .NET 4.0.30319.1

The ir is 32bit version of IronRuby, if you want to use 64bit you can try ir64.

Next, we have to find a editor where we can write our Ruby code as there is currently no integration story of IronRuby with Visual Studio like its twin Iron Python. Among the free IDEs only SharpDevelop has the IronRuby support but it does not have auto complete or debugging built into it, only thing that it supports is the syntax highlighting, so using a text editor which has the same features is nothing different comparing to it. To play with the IronRuby we will be using Notepad++, which can be downloaded from its sourceforge download page. The Notepad++ does have a nice syntax highlighting support :

npplusplus-ruby

I am using the Vibrant Ink with some little modification.

The next thing we have to do is configure the Notepad++ that we can run the Ruby script in IronRuby inside the Notepad++. Lets create a batch(.bat) file in the IronRuby bin directory, which will have the following content:

@echo off 
cls
call ir %1
pause

This will make sure that the console will be paused once we run the script.

Now click Run->Run in the Notepad++, it will bring up the run dialog and put the following command in the textbox (The riir.bat is the batch file which we have saved in the above):

riir.bat "$(FULL_CURRENT_PATH)"

npplusplus-ruby-run

Click the save which will bring another dialog.

Type Iron Ruby and assign the shortcut to ctrl + f5 (Same as Visual Studio Start without Debugging) and click ok.

npplusplus-ruby-shortcut

Once you are done you will find the IronRuby in the Run menu. Now press ctrl + f5, we will find the ruby script running in the IronRuby.

npplusplus-ruby-output

Now there are one last thing that we would like to add which is poor man’s context sensitive help. First, download the ruby language help file from the Ruby Installer site and extract into a directory. Next we will have to install the Language Help Plug-in of Notepad++, click Plugins->Plugin Manger –>Show Plugin Manager and scroll down until you find the plug-in the list, now check the plug-in and click install. Once it is installed it will prompt you to restart the Notepad++, click yes.

npplusplus-ruby-plugin

When the Notepad++ restarts, click the Plugins –> Language Help –> Options –> add and enter the following details and click ok:

npplusplus-ruby-help

The chm file location can be different depending upon where you extracted it.

Now when you put your in any of ruby keyword and press ctrl + f1 it will take you to the help topic of that keyword.

For example, when my caret is in the each of the following code and I press ctrl + f1, it will take me to the each api doc of Array.

def loop_demo
	(1..10).each{ |n| puts n}
end

loop_demo

That’s it for today.

Happy Ruby coding.

Shout it

When using the DisplayFor/EditorFor there has been built-in support in ASP.NET MVC to show localized validation messages, but no support to show the associate label in localized text, unless you are using the .NET 4.0 with Mvc Future. Lets a say you are creating a create form for Product where you have support both English and German like the following.

English

create-en

German

create-de

I have recently added few helpers for localization in the MvcExtensions, lets see how we can use it to localize the form. As mentioned in the past that I am not a big fan when it comes to decorate class with attributes which is the recommended way in ASP.NET MVC. Instead, we will use the fluent configuration (Similar to FluentNHibernate or EF CodeFirst) of MvcExtensions to configure our View Models. For example for the above we will using:

public class ProductEditModelConfiguration : ModelMetadataConfiguration<ProductEditModel>
{
    public ProductEditModelConfiguration()
    {
        Configure(model => model.Id).Hide();

        Configure(model => model.Name).DisplayName(() => LocalizedTexts.Name)
                                      .Required(() => LocalizedTexts.NameCannotBeBlank)
                                      .MaximumLength(64, () => LocalizedTexts.NameCannotBeMoreThanSixtyFourCharacters);

        Configure(model => model.Category).DisplayName(() => LocalizedTexts.Category)
                                          .Required(() => LocalizedTexts.CategoryMustBeSelected)
                                          .AsDropDownList("categories", () => LocalizedTexts.SelectCategory);

        Configure(model => model.Supplier).DisplayName(() => LocalizedTexts.Supplier)
                                          .Required(() => LocalizedTexts.SupplierMustBeSelected)
                                          .AsListBox("suppliers");

        Configure(model => model.Price).DisplayName(() => LocalizedTexts.Price)
                                       .FormatAsCurrency()
                                       .Required(() => LocalizedTexts.PriceCannotBeBlank)
                                       .Range(10.00m, 1000.00m, () => LocalizedTexts.PriceMustBeBetweenTenToThousand);
    }
}

As you can we are using Func<string> to set the localized text, this is just an overload with the regular string method. There are few more methods in the ModelMetadata which accepts this Func<string> where localization can applied like Description, Watermark, ShortDisplayName etc. The LocalizedTexts is just a regular resource, we have both English and German:

mvc-localization-se

 

Now lets see the view markup:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<Demo.Web.ProductEditModel>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    <%= LocalizedTexts.Create %>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2><%= LocalizedTexts.Create %></h2>
    <%= Html.ValidationSummary(false, LocalizedTexts.CreateValidationSummary)%>
    <% Html.EnableClientValidation(); %>
    <% using (Html.BeginForm()) {%>
        <fieldset>
            <%= Html.EditorForModel() %>
            <p>
                <input type="submit" value="<%= LocalizedTexts.Create %>" />
            </p>
        </fieldset>
    <% } %>
    <div>
        <%= Html.ActionLink(LocalizedTexts.BackToList, "Index")%>
    </div>
</asp:Content>

As we can see that we are using the same LocalizedTexts for the other parts of the view which is not included in the ModelMetadata like the Page title, button text etc. We are also using EditorForModel instead of EditorFor for individual field and both are supported.

One of the added benefit of the fluent syntax based configuration is that we will get full compile type checking for our resource as we are not depending upon the string based resource name like the ASP.NET MVC.

You will find the complete localized CRUD example in the MvcExtensions sample folder [Available in the Trunk].

That’s it for today.

Shout it
More Posts