<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Web &#8211; Other Things</title>
	<atom:link href="https://blog.adamzolo.com/category/web/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.adamzolo.com</link>
	<description>Blog about Things by Adam Zolotarev</description>
	<lastBuildDate>Sun, 08 Feb 2026 21:08:42 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>
	<item>
		<title>Building Clausy: A Contract Analysis Tool with Rails 8 and Claude AI</title>
		<link>https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/</link>
					<comments>https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Sun, 08 Feb 2026 21:07:19 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">https://blog.adamzolo.com/?p=1090</guid>

					<description><![CDATA[I just launched https://clausyapp.com, a web app that uses AI to analyze contracts and highlight potential issues. You upload a PDF/images or paste text, and Claude AI reads through it to find things like unlimited liability clauses, auto-renewal terms, or aggressive IP assignment language. Why I Built This I&#8217;ve signed a enough contracts over the&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/" title="Continue reading &#8216;Building Clausy: A Contract Analysis Tool with Rails 8 and Claude AI&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[
<p>I just launched https://clausyapp.com, a web app that uses AI to analyze contracts and highlight potential issues. You upload a PDF/images or paste text, and Claude AI reads through it to find things like unlimited liability clauses, auto-renewal terms, or aggressive IP assignment language.</p>



<h2 class="wp-block-heading">Why I Built This</h2>



<p>I&#8217;ve signed a enough contracts over the years, and I was never quite sure if I was missing something important buried in the legal language. I&#8217;d skim through them, but let&#8217;s be honest – I didn&#8217;t understand half of it. Getting a lawyer to review every contract is also just not something I&#8217;m going to do, unless it&#8217;s something really big.</p>



<p>I figured: AI is pretty good at reading and understanding text now. And based on how many of these AI contract analysis tools are out there, it&#8217;s the new TODO app in the age of AI.</p>



<h2 class="wp-block-heading">The Stack</h2>



<p>I went with a Rails 8 monolith because I wanted something I could ship quickly and maintain solo:</p>



<ul class="wp-block-list">
<li>Rails 8 with Hotwire (Turbo + Stimulus)</li>



<li>Anthropic&#8217;s Claude API</li>



<li>Solid Queue for background jobs with priority queues (paid users get faster processing)</li>



<li>Solid Cache for caching and rate limits</li>



<li>Stripe for subscriptions and billing</li>



<li>Tesseract OCR for extracting text from scanned images (JPG, PNG, WebP, HEIC)</li>



<li>Kamal for deployment</li>
</ul>



<p>Everything runs in Docker containers. No separate frontend framework, no microservices. Just a straightforward Rails app that does one thing well.</p>



<h2 class="wp-block-heading">Technical Security Challenges</h2>



<ol class="wp-block-list">
<li><strong>File Processing Security</strong>
<ul class="wp-block-list">
<li>Magic byte validation – Don&#8217;t trust file extensions. I check the actual file signature to verify it&#8217;s really a PDF or DOCX.</li>



<li>Size limits – DOCX files are zip archives, so I enforce size limits before decompression to prevent zip bombs.</li>



<li>Immediate deletion – Original files are deleted right after text extraction. No long-term storage of sensitive documents.</li>



<li>Command injection prevention – Only use safe extraction tools, never shell out with user-provided filenames.</li>
</ul>
</li>



<li><strong>Server Access</strong>
<ul class="wp-block-list">
<li>Firewall at the provider level </li>



<li>Firewall at the node level (ufw)</li>



<li>ssh through certs only, limit access to specific IP&#8217;s</li>



<li>Cloudflare</li>
</ul>
</li>



<li><strong>Application </strong>Security
<ul class="wp-block-list">
<li>Devise authentication &#8211; Industry-standard auth framework</li>



<li>CSRF protection &#8211; Rails CSRF tokens on all POST/PUT/PATCH/DELETE requests</li>



<li>UUID-based URLs &#8211; Guest contracts use UUIDs (prevents enumeration attacks)</li>



<li>Rate Limits (Rack::Attack)</li>



<li>CSP Policy
<ul class="wp-block-list">
<li>No unsafe-eval &#8211; Prevents eval() attacks</li>



<li>Whitelisted script sources &#8211; Only self, HTTPS, Stripe, Cloudflare allowed</li>



<li>No object embeds &#8211; object_src :none blocks Flash/plugin attacks</li>



<li>Nonce-based scripts &#8211; Importmap scripts use session-based nonces</li>



<li>HTTPS enforced &#8211; All resources loaded over HTTPS</li>
</ul>
</li>



<li>Input Validation</li>
</ul>
</li>



<li>Fraud Prevention
<ul class="wp-block-list">
<li>Email history tracking &#8211; SHA256 email hashing &#8211; Email hashes stored, not plain emails</li>
</ul>
</li>



<li>Payment Security
<ul class="wp-block-list">
<li>Stripe webhook verification &#8211; Signature validation on all webhook events</li>



<li>No card storage &#8211; Stripe handles all payment details</li>
</ul>
</li>



<li>Secret Management
<ul class="wp-block-list">
<li>Rails credentials &#8211; All secrets in encrypted credentials.yml.enc</li>
</ul>
</li>



<li>XSS Prevention
<ul class="wp-block-list">
<li>Automatic HTML escaping</li>



<li>CSP headers &#8211; Content Security Policy blocks inline scripts</li>
</ul>
</li>



<li>Transport Security
<ul class="wp-block-list">
<li>HTTPS everywhere &#8211; All resources loaded over HTTPS</li>



<li>Secure cookies &#8211; Session cookies marked secure in production</li>



<li>HSTS headers &#8211; Forces HTTPS connections</li>
</ul>
</li>



<li>DoS Prevention
<ul class="wp-block-list">
<li>Job queues &#8211; Background processing prevents request timeouts</li>



<li>Priority queues &#8211; Paid users get separate high-priority queue</li>



<li>Rate limiting &#8211; Comprehensive rate limits across all endpoints</li>



<li>Query optimization &#8211; Indexed queries prevent slow lookups</li>
</ul>
</li>
</ol>



<h2 class="wp-block-heading">Try it</h2>



<p>Demo (no signup): <a href="https://clausyapp.com/contracts/new?demo=hn">https://clausyapp.com/contracts/new?demo=hn</a></p>



<p>Full app: <a href="https://clausyapp.com">https://clausyapp.com</a></p>



<p>It&#8217;s not legal advice – I&#8217;m very explicit about that – but it can help you spot things you might want to ask a lawyer about.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/building-clausy-a-contract-analysis-tool-with-rails-8-and-claude-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Elixir Phoenix Cache</title>
		<link>https://blog.adamzolo.com/elixir-phoenix-cache/</link>
					<comments>https://blog.adamzolo.com/elixir-phoenix-cache/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Mon, 29 Jan 2018 00:53:01 +0000</pubDate>
				<category><![CDATA[Elixir]]></category>
		<category><![CDATA[Phoenix]]></category>
		<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=717</guid>

					<description><![CDATA[An implementation with ets. Let&#8217;s start with implementation Update application.ex Finally, use it Relavent links: https://stackoverflow.com/questions/35218738/caching-expensive-computation-in-elixir https://dockyard.com/blog/2017/05/19/optimizing-elixir-and-phoenix-with-ets]]></description>
										<content:encoded><![CDATA[<p>An implementation with ets. </p>
<p>Let&#8217;s start with implementation</p>
<pre class="brush: plain; title: ; notranslate">
defmodule SimpleCache do
  @table :simple_cache

  def init(_) do
    :ets.new(@table, &#x5B;
      :set,
      :named_table,
      :public,
      read_concurrency: true,
      write_concurrency: true
    ])

    {:ok, %{}}
  end

  def start_link do
    GenServer.start_link(__MODULE__, &#x5B;], name: __MODULE__)
  end

  def fetch(key, expires_in_seconds, fun) do
    case lookup(key) do
      {:hit, value} -&gt;
        value

      :miss -&gt;
        value = fun.()
        put(key, expires_in_seconds, value)
        value
    end
  end

  defp lookup(key) do
    case :ets.lookup(@table, key) do
      &#x5B;{^key, expires_at, value}] -&gt;
        case now &lt; expires_at do
          true -&gt; {:hit, value}
          false -&gt; :miss
        end

      _ -&gt;
        :miss
    end
  end

  defp put(key, expires_in_seconds, value) do
    expires_at = now + expires_in_seconds
    :ets.insert(@table, {key, expires_at, value})
  end

  defp now do
    :erlang.system_time(:seconds)
  end
end


</pre>
<p>Update application.ex</p>
<pre class="brush: plain; title: ; notranslate">
 def start(_type, _args) do
    import Supervisor.Spec

    children = &#x5B;
      supervisor(SimpleCache, &#x5B;])
    ]
    opts = &#x5B;strategy: :one_for_one, name: Supervisor]
    Supervisor.start_link(children, opts)
  end

</pre>
<p>Finally, use it</p>
<pre class="brush: plain; title: ; notranslate">
    cache_for_seconds = 60
    key = 'key'

    SimpleCache.fetch(key, cache_for_seconds, fn -&gt;
      {:ok, some_expensive_operation}
    end)
</pre>
<p>Relavent links:<br />
<a href="https://stackoverflow.com/questions/35218738/caching-expensive-computation-in-elixir" rel="noopener" target="_blank">https://stackoverflow.com/questions/35218738/caching-expensive-computation-in-elixir</a><br />
<a href="https://dockyard.com/blog/2017/05/19/optimizing-elixir-and-phoenix-with-ets" rel="noopener" target="_blank">https://dockyard.com/blog/2017/05/19/optimizing-elixir-and-phoenix-with-ets</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/elixir-phoenix-cache/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>RSpec, Action Cable, and Capybara (As of Rails 5.1.2)</title>
		<link>https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/</link>
					<comments>https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Tue, 04 Jul 2017 03:19:28 +0000</pubDate>
				<category><![CDATA[Rails]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">http://blog.adamzolo.com/?p=628</guid>

					<description><![CDATA[Gems in Gemfile: group :test, :development do gem 'database_cleaner' gem &#34;rspec-rails&#34;, &#34;~&#62; 3.6.0&#34; gem 'selenium-webdriver' end group :test do gem &#34;capybara&#34;, &#34;~&#62; 2.14.0&#34; end rails_helper: config.use_transactional_fixtures = false config.before(:suite) do DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.strategy = :transaction end config.before(:each, js: true) do DatabaseCleaner.strategy = :truncation end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end #Puma&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/" title="Continue reading &#8216;RSpec, Action Cable, and Capybara (As of Rails 5.1.2)&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>Gems in Gemfile:</p>
<pre class="brush: ruby; title: ; notranslate">
group :test, :development do
  gem 'database_cleaner'
  gem &quot;rspec-rails&quot;, &quot;~&gt; 3.6.0&quot;
  gem 'selenium-webdriver'
end

group :test do
  gem &quot;capybara&quot;, &quot;~&gt; 2.14.0&quot;
end
</pre>
<p>rails_helper:</p>
<pre class="brush: ruby; title: ; notranslate">
config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, js: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end

  #Puma makes it possible to run RSpec with ActionCable
  Capybara.server = :puma

  Capybara.register_driver :selenium_chrome do |app|
    Capybara::Selenium::Driver.new(app, browser: :chrome)
  end
  Capybara.javascript_driver = :selenium_chrome

</pre>
<p>And, the <a href="https://sites.google.com/a/chromium.org/chromedriver/" target="_blank">driver</a>:</p>
<pre class="brush: plain; title: ; notranslate">
brew install chromedriver
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/rspec-action-cable-capybara-rails-5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>From .Net Developer to Elm. Part 2 – Making a Simple Project.</title>
		<link>https://blog.adamzolo.com/making-simple-elm-project/</link>
					<comments>https://blog.adamzolo.com/making-simple-elm-project/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Mon, 11 Apr 2016 11:46:33 +0000</pubDate>
				<category><![CDATA[Elm]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">http://www.eazolo.com/blog/?p=243</guid>

					<description><![CDATA[In this article I&#8217;ll write a simple Bayesian Calculator using Elm. Link to final project &#8211; Bayesian Calculator &#8211; Source Code Create a directory for your project. cd into it. Run &#8220;elm-make&#8221; to initialize a new elm project. Install some packages for working with web: elm-package install &#8220;evancz/elm-html&#8221; elm-package install &#8220;evancz/start-app&#8221; start-app takes care of&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/making-simple-elm-project/" title="Continue reading &#8216;From .Net Developer to Elm. Part 2 – Making a Simple Project.&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>In this article I&#8217;ll write a simple Bayesian Calculator using Elm.<br />
Link to final project &#8211; <a href="https://www.eazolo.com/apps/bayesiancalculator/calculator.html" target="_blank">Bayesian Calculator</a> &#8211; <a href="https://github.com/adamzolotarev/elm-bayesian-calculator" target="_blank">Source Code</a></p>
<p>Create a directory for your project.<br />
cd into it.<br />
Run &#8220;elm-make&#8221; to initialize a new elm project.<br />
Install some packages for working with web:</p>
<p>elm-package install &#8220;evancz/elm-html&#8221;<br />
elm-package install &#8220;evancz/start-app&#8221;</p>
<p>start-app takes care of some application wiring,&#8221;<a href="https://github.com/evancz/start-app" target="_blank">setting everything up so you can focus entirely on writing your app</a>&#8221;</p>
<p>If you open elm-package.json, you will see that the packages, which we just installed, were added to &#8220;dependencies&#8221; node.</p>
<p>I&#8217;ll use npm and gulp to manage my build. Also, I&#8217;m using bootstrap (cause css is hard)<br />
npm init<br />
npm install bootstrap &#8211;save-dev<br />
npm install gulp &#8211;save-dev<br />
npm install gulp-elm &#8211;save-dev<br />
npm install gulp-rename &#8211;save-dev</p>
<p>The last piece of setting up is gulp. In the <a href="https://github.com/adamzolotarev/elm-bayesian-calculator/blob/master/gulpfile.js" target="_blank">script</a>, I just combine all css and js into bundles and copy them to dest folder.</p>
<p>We&#8217;re ready to start writing our app.</p>
<p>Create <a href="https://github.com/adamzolotarev/elm-bayesian-calculator/blob/master/src/Calculator.elm" target="_blank">Calculator.elm</a>. This is where our code will go.</p>
<p>Create <a href="https://github.com/adamzolotarev/elm-bayesian-calculator/blob/master/src/Calculator.html" target="_blank">Calculator.html</a>. This will encapsulate resulting JavaScript file from Elm. Since I&#8217;m writing a full-screen application, all we have to do is add this to the script block:</p>
<p>var app = Elm.fullscreen(Elm.Calculator);</p>
<p>Now, to the application code.</p>
<p>Our model is quite simple &#8211; consists of four string fields related to four input fields on the form.<br />
We have three inputs on the page &#8211; all encapsulated inside entryForm:</p>
<pre class="brush: plain; title: ; notranslate">
entryForm : Address Action -&gt; Model -&gt; Html
entryForm address model =
  div
    &#x5B; class &quot;form-horizontal&quot; ]
    &#x5B; formGroupItem (priorProbabilityItem address model)
    , formGroupItem (hitRateItem address model)
    , formGroupItem (falseAlarmItem address model)
    , formGroupItem (resultItem address model)
    ]
</pre>
<p>formGroupItem is simply a method to produce another div with a class of &#8220;form-group&#8221;:</p>
<pre class="brush: plain; title: ; notranslate">
formGroupItem : List Html -&gt; Html
formGroupItem body =
  div
    &#x5B; class &quot;form-group&quot; ]
    body
</pre>
<p>body in here is just a list of Html elements that we are passing to our div.</p>
<p>Let&#8217;s look at one of the inputs: </p>
<pre class="brush: plain; title: ; notranslate">
priorProbabilityItem : Address Action -&gt; Model -&gt; List Html
priorProbabilityItem address model =
  &#x5B; label
      &#x5B; for &quot;priorProbability&quot;, class &quot;col-md-3 control-label&quot; ]
      &#x5B; text &quot;Prior Probability&quot; ]
  , div
      &#x5B; class &quot;col-md-9&quot; ]
      &#x5B; input
          &#x5B; type' &quot;text&quot;
          , placeholder &quot;0.00&quot;
          , value model.priorProbability
          , id &quot;priorProbability&quot;
          , autofocus True
          , Utils.onInput address UpdateInitialProbability
          ]
          &#x5B;]
      ]
  ]
</pre>
<p>We have a label and a div with an input which maps to priorProbability field in our model.<br />
Every time a user enters something we react to it by addressing UpdateInitialProbability action:</p>
<pre class="brush: plain; title: ; notranslate">
update : Action -&gt; Model -&gt; Model
update action model =
  case action of
...
    UpdateInitialProbability probability -&gt;
      { model | priorProbability = probability }
...
</pre>
<p>In the code above we are updating our model with the new value that a user entered for priorProbability.<br />
We have the same logic for all of the other input fields.</p>
<p>Every time a model changes, we try to show the Bayes calculation result:</p>
<pre class="brush: plain; title: ; notranslate">
resultItem : Address Action -&gt; Model -&gt; List Html
resultItem address model =
  &#x5B; label
      &#x5B; for &quot;bayesResult&quot;, class &quot;col-md-3 control-label&quot; ]
      &#x5B; text &quot;New Probability&quot; ]
  , div
      &#x5B; class &quot;col-md-9&quot; ]
      &#x5B; span &#x5B;] &#x5B; text (bayesResult model) ]
      ]
  ]
</pre>
<p>bayesResult is responsible for generating new probability. If some input is invalid, we just don&#8217;t display anything. Sure, validation could be a little more sophisticated, but we&#8217;re just trying it out.</p>
<p>While in development, make sure to run elm-reactor and gulp in the command line &#8211; open two tabs or windows (depending on your console app) for each one and just run &#8220;elm-reactor&#8221; in one, and &#8220;gulp&#8221; in another. </p>
<p>In order to see your app in a browser, go to http://localhost:8000/dest/Calculator.html<br />
Every time you save your elm file, it should update the js files in the dest folder. You still have to refresh the browser to see the changes. I haven&#8217;t quite figured the auto-refresh part with gulp and elm.</p>
<p>If you want to see the build errors, they should show up in the console, which runs gulp command. Alternatively, if you use Sublime, press Ctrl + B to build it and all errors should show up directly in Sublime.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/making-simple-elm-project/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Custom MVC Model Binder</title>
		<link>https://blog.adamzolo.com/custom-mvc-model-binder/</link>
					<comments>https://blog.adamzolo.com/custom-mvc-model-binder/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Wed, 02 Dec 2015 18:19:39 +0000</pubDate>
				<category><![CDATA[C#]]></category>
		<category><![CDATA[MVC]]></category>
		<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">http://www.eazolo.com/blog/?p=164</guid>

					<description><![CDATA[When you expect data in non-standard way and you want it to magically bind to your model, custom model binders could be a way to go. Let&#8217;s say we want to post xml data to our controller method and we have a model for it. public class XmlModel { public int Number { get; set;&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/custom-mvc-model-binder/" title="Continue reading &#8216;Custom MVC Model Binder&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>When you expect data in non-standard way and you want it to magically bind to your model, custom model binders could be a way to go.</p>
<p>Let&#8217;s say we want to post xml data to our controller method and we have a model for it.</p>
<pre class="brush: csharp; title: ; notranslate">
public class XmlModel
        {
            public int Number { get; set; }
        }
</pre>
<p>Which we want to accept in our controller method:</p>
<pre class="brush: csharp; title: ; notranslate">
public ActionResult Xml(XmlModel xmlModel)
        {
            return Content(xmlModel.Number.ToString());
        }

</pre>
<p>Now, let&#8217;s add our xml binder:</p>
<pre class="brush: csharp; title: ; notranslate">
public class XmlModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            var modelType = bindingContext.ModelType;
            return new XmlSerializer(modelType).Deserialize(controllerContext.HttpContext.Request.InputStream);
        }
    }
</pre>
<p>And XMLModelBinderProvider:</p>
<pre class="brush: csharp; title: ; notranslate">
public class XMLModelBinderProvider : IModelBinderProvider
    {      
        public IModelBinder GetBinder(Type modelType)
        {
            var contentType = HttpContext.Current.Request.ContentType;
            if (string.Compare(contentType, @&quot;text/xml&quot;, StringComparison.OrdinalIgnoreCase) != 0)
            {
                return null;
            }

            return new XmlModelBinder();
        }
    }
</pre>
<p>Add the provider to our Application_Start method in the Global.asax:</p>
<pre class="brush: csharp; title: ; notranslate">
ModelBinderProviders.BinderProviders.Insert(0, new XMLModelBinderProvider()); 
</pre>
<p>To test this we can use Composer from Fiddler:<br />
http://localhost/home/xml<br />
User-Agent: Fiddler<br />
Content-Type: text/xml</p>
<p>Request Body:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;XmlModel&gt;
&lt;Number&gt;42&lt;/Number&gt;
&lt;/XmlModel&gt;
</pre>
<p>If we add breakpoint in our controller, we should see 42 coming through in our model.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/custom-mvc-model-binder/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Chrome Developer Tools Productivity Tips</title>
		<link>https://blog.adamzolo.com/chrome-developer-tools-productivity-tips/</link>
					<comments>https://blog.adamzolo.com/chrome-developer-tools-productivity-tips/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Mon, 02 Feb 2015 01:41:35 +0000</pubDate>
				<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">http://eazolo.com/blog/?p=119</guid>

					<description><![CDATA[Most Useful Shortcuts Ctrl Shift I &#8211; Open developer tools. Ctrl Shift P &#8211; Search within file. Displays all functions within a file. Ctrl P &#8211; show js files. Type to search. Ctrl Shift C &#8211; inspect element Esc &#8211; toggle console Ctrl F &#8211; find inside a file Ctrl Shift F &#8211; find across&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/chrome-developer-tools-productivity-tips/" title="Continue reading &#8216;Chrome Developer Tools Productivity Tips&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>Most Useful Shortcuts<br />
Ctrl Shift I &#8211; Open developer tools.<br />
Ctrl Shift P &#8211; Search within file. Displays all functions within a file.<br />
Ctrl P &#8211; show js files. Type to search.<br />
Ctrl Shift C &#8211; inspect element<br />
Esc &#8211; toggle console<br />
Ctrl F &#8211; find inside a file<br />
Ctrl Shift F &#8211; find across all sources</p>
<p>To enable text editing directly in a browser:<br />
document.body.contentEditable = true;</p>
<p>To time something: console.time(&#8220;my action&#8221;);  &#8230; console.timeEnd(&#8220;my action&#8221;)</p>
<p>Snippets &#8211; run custom JavaScript code and run it anwhere you want. <a href="https://bgrins.github.io/devtools-snippets/" title="A collection of useful snippets" target="_blank">https://bgrins.github.io/devtools-snippets/</a></p>
<p>To disable cache &#8211; Network tab &#8211; disable cache (while DevTools is open).</p>
<p>Add timestamp to events displayed in console &#8211; Settings &#8211; General &#8211; Console &#8211; Show timestamps.</p>
<p>Enabling awesome dark theme:<br />
Go to chrome://flags &#8211; Enable Developer Tools Experiments.<br />
Open Chrome Dev Tools. &#8211; Settings &#8211; Experiments &#8211; allow custom UIthemes<br />
Install a DevTools Theme. <a href="https://chrome.google.com/webstore/detail/devtools-theme-zero-dark/bomhdjeadceaggdgfoefmpeafkjhegbo" title="Here's one" target="_blank">Here&#8217;s one</a>.</p>
<p>A cool video about the above and much more &#8211; <a href="http://www.ndcvideos.com/#/app/video/3371" title="Chrome Developer Tools Deep Dive by Shay Friedman" target="_blank">Chrome Developer Tools Deep Dive by Shay Friedman</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/chrome-developer-tools-productivity-tips/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Passing custom objects to FluentValidation</title>
		<link>https://blog.adamzolo.com/passing-custom-objects-fluentvalidation/</link>
					<comments>https://blog.adamzolo.com/passing-custom-objects-fluentvalidation/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Sat, 20 Sep 2014 15:28:05 +0000</pubDate>
				<category><![CDATA[MVC]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[FluentValidation]]></category>
		<guid isPermaLink="false">http://eazolo.com/blog/?p=86</guid>

					<description><![CDATA[This is a guide for passing custom objects to Fluent Validators. This can be helpful for more complex validation scenarios. Here’s an example of what we want to achieve: public class OurModelValidator: AbstractValidator&#60;OurMVCViewModel&#62; { public OurModelValidator(OurClassWeWantToPassToValidator ourClassWeWantToPassToValidator) { if (ourClassWeWantToPassToValidator.SomeCondition) { RuleFor(m =&#62; m.PropertyA).NotEmpty(); } else { RuleFor(m =&#62; m.PropertyB).NotEmpty(); } } } In order&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/passing-custom-objects-fluentvalidation/" title="Continue reading &#8216;Passing custom objects to FluentValidation&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>This is a guide for passing custom objects to Fluent Validators. This can be helpful for more complex validation scenarios.</p>
<p>Here’s an example of what we want to achieve:</p>
<pre class="brush: csharp; title: ; notranslate">
public class OurModelValidator: AbstractValidator&lt;OurMVCViewModel&gt;
{
public OurModelValidator(OurClassWeWantToPassToValidator ourClassWeWantToPassToValidator)
{
if (ourClassWeWantToPassToValidator.SomeCondition)
{
RuleFor(m =&gt; m.PropertyA).NotEmpty();
}
else
{
RuleFor(m =&gt; m.PropertyB).NotEmpty();
}
}
}
</pre>
<p>In order to achieve this, we need to make some changes to the FluentValidationModelValidatorProvider class by overriding its CreateValidator method:</p>
<pre class="brush: csharp; title: ; notranslate">
public class OurCustomFluentModelValidatorProvider : FluentValidationModelValidatorProvider
{

protected override IValidator CreateValidator(ModelMetadata metadata, ControllerContext context)
{
var type = this.IsValidatingProperty(metadata) ? metadata.ContainerType : metadata.ModelType;
return GetOurModelValidator(type) ?? this.ValidatorFactory.GetValidator(type);
}

private IValidator GetOurModelValidator(Type modelType)
{
if (modelType == typeof(OurClassWeWantToPassToValidator))
{
var ourClassWeWantToPassToValidator = new OurClassWeWantToPassToValidator();
return new OurModelValidator(ourClassWeWantToPassToValidator);
}

return null;
}
}
</pre>
<p>Since we want FluentValidation to handle all situation when OurModelValidator is not applicable, we use built-in ValidatorFactory to return default Validator when validating anything but OurMVCViewModel.</p>
<p>And the last step is to register our new provider:</p>
<pre class="brush: csharp; title: ; notranslate">
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new DataAnnotationsModelValidatorProvider());
ModelValidatorProviders.Providers.Add(new DataErrorInfoModelValidatorProvider());
ModelValidatorProviders.Providers.Add(new ClientDataTypeModelValidatorProvider());
ModelValidatorProviders.Providers.Add(new OurCustomFluentModelValidatorProvider());
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/passing-custom-objects-fluentvalidation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>FluentValidation Client-Side Custom Validation with MVC</title>
		<link>https://blog.adamzolo.com/fluentvalidation-client-side-custom-validation/</link>
					<comments>https://blog.adamzolo.com/fluentvalidation-client-side-custom-validation/#comments</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Fri, 23 May 2014 02:51:03 +0000</pubDate>
				<category><![CDATA[MVC]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[FluentValidation]]></category>
		<guid isPermaLink="false">http://eazolo.com/blog/?p=54</guid>

					<description><![CDATA[FluentValidation is &#8220;A small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects.&#8221; It is quite awesome. However, the provided client-side validation is limited:  &#8220;any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side.&#8221; Below&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/fluentvalidation-client-side-custom-validation/" title="Continue reading &#8216;FluentValidation Client-Side Custom Validation with MVC&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>FluentValidation is &#8220;<span style="color: #253340;"><a href="http://fluentvalidation.codeplex.com/" target="_blank">A small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects.</a>&#8221; It is quite awesome. However, the provided client-side validation is limited:  &#8220;<a href="http://fluentvalidation.codeplex.com/wikipage?title=mvc&amp;referringTitle=Documentation" target="_blank">any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side.</a>&#8221; Below is some my first take on using FluentValidation on both server and client-side.</span></p>
<p>First, you need to install the nuget package. In our case, it is FluentValidation.MVC4.</p>
<p>We needed to make a certain property required, but only when some other property is true. To achieve this on client-side (server-side is trivial), we added these:</p>
<pre class="brush: csharp; title: ; notranslate">
	public class RequiredIfClientSideValidator : PropertyValidator
	{
		public string DependentProperty { get; set; }
		public object TargetValue { get; set; }

		public RequiredIfClientSideValidator(string errorMessage, string dependentProperty, object targetValue)
			: base(errorMessage)
		{
			this.DependentProperty = dependentProperty;
			this.TargetValue = targetValue;
		}

		protected override bool IsValid(PropertyValidatorContext context)
		{
			//This is not a server side validation rule. So, should not effect at the server side.  
			return true;
		}
	}
</pre>
<p>And this</p>
<pre class="brush: csharp; title: ; notranslate">
	public class RequiredIfClientSideFluentPropertyValidator : FluentValidationPropertyValidator
	{
		private RequiredIfClientSideValidator RequiredIfClientSideValidator
		{
			get { return (RequiredIfClientSideValidator)Validator; }
		}

		public RequiredIfClientSideFluentPropertyValidator(ModelMetadata metadata,
													   ControllerContext controllerContext,
													   PropertyRule propertyDescription,
													   IPropertyValidator validator)
			: base(metadata, controllerContext, propertyDescription, validator)
		{
			ShouldValidate = false;
		}

		public override IEnumerable&lt;ModelClientValidationRule&gt; GetClientValidationRules()
		{
			if (!ShouldGenerateClientSideRules()) yield break;

			var formatter = new MessageFormatter().AppendPropertyName(Rule.GetDisplayName());
			string message = formatter.BuildMessage(RequiredIfClientSideValidator.ErrorMessageSource.GetString());

			var rule = new ModelClientValidationRule()
			{
				ValidationType = &quot;requiredif&quot;,
				ErrorMessage = message
			};

			string depProp = BuildDependentPropertyId(Metadata, ControllerContext as ViewContext);
			// find the value on the control we depend on;
			// if it's a bool, format it javascript style 
			// (the default is True or False!)
			string targetValue = (RequiredIfClientSideValidator.TargetValue ?? &quot;&quot;).ToString();
			if (RequiredIfClientSideValidator.TargetValue.GetType() == typeof(bool))
				targetValue = targetValue.ToLower();

			rule.ValidationParameters.Add(&quot;dependentproperty&quot;, depProp);
			rule.ValidationParameters.Add(&quot;targetvalue&quot;, targetValue);

			yield return rule;
		}

		private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
		{
			// build the ID of the property
			string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(RequiredIfClientSideValidator.DependentProperty);
			// unfortunately this will have the name of the current field appended to the beginning,
			// because the TemplateInfo's context has had this fieldname appended to it. Instead, we
			// want to get the context as though it was one level higher (i.e. outside the current property,
			// which is the containing object (our Person), and hence the same level as the dependent property.
			var thisField = metadata.PropertyName + &quot;_&quot;;
			if (depProp.StartsWith(thisField))
				// strip it off again
				depProp = depProp.Substring(thisField.Length);
			return depProp;
		}
	}
</pre>
<p>Additionally, we introduced a validator for simple client-side validation (ClientSideValidator) where you just need to pass an error message to your JavaScript validation.</p>
<pre class="brush: csharp; title: ; notranslate">
public class ClientSideValidator : PropertyValidator
	{
		public string JavascriptValidationAdapterName { get; set; }

		public ClientSideValidator(string errorMessage, string javascriptValidationFunctionName)
			: base(errorMessage)
		{
			JavascriptValidationAdapterName = javascriptValidationFunctionName;
		}

		protected override bool IsValid(PropertyValidatorContext context)
		{
			//This is not a server side validation rule. So, should not effect at the server side.  
			return true;
		}
	}
</pre>
<p>And an accompanying class: </p>
<pre class="brush: csharp; title: ; notranslate">	
	public class ClientSideFluentValidator : FluentValidationPropertyValidator
	{
		private ClientSideValidator ClientSideValidator
		{
			get { return (ClientSideValidator)Validator; }
		}

		public ClientSideFluentValidator(ModelMetadata metadata,
			ControllerContext controllerContext,
			PropertyRule propertyDescription,
			IPropertyValidator validator)
			: base(metadata, controllerContext, propertyDescription, validator)
		{
			ShouldValidate = false;
		}

		public override IEnumerable&lt;ModelClientValidationRule&gt; GetClientValidationRules()
		{
			if (!ShouldGenerateClientSideRules()) yield break;

			var formatter = new MessageFormatter().AppendPropertyName(Rule.GetDisplayName());
			string message = formatter.BuildMessage(ClientSideValidator.ErrorMessageSource.GetString());

			var rule = new ModelClientValidationRule()
			{
				ValidationType = ClientSideValidator.JavascriptValidationAdapterName,
				ErrorMessage = message
			};
			
			yield return rule;
		}
	}
</pre>
<p>Register your custom validators in Global.asax.cs:</p>
<pre class="brush: csharp; title: ; notranslate">
FluentValidationModelValidationFactory requiredIfClientSideValidationFactory =
				(metadata, context, rule, validator) =&gt;
					new RequiredIfClientSideFluentPropertyValidator(metadata, context, rule, validator);
			
			FluentValidationModelValidationFactory clientSideValidationFactory =
				(metadata, context, rule, validator) =&gt;
					new ClientSideFluentValidator(metadata, context, rule, validator);
			
			FluentValidationModelValidatorProvider.Configure(provider =&gt;
			{
				provider.Add(typeof(RequiredIfClientSideValidator), requiredIfClientSideValidationFactory);
				provider.Add(typeof(ClientSideValidator), clientSideValidationFactory);
			});
</pre>
<p>Don&#8217;t forget to add the JavaScript part:<br />
For requiredif validation:</p>
<pre class="brush: jscript; title: ; notranslate">
$.validator.addMethod('requiredif',
    function (value, element, parameters) {
    	var id = '#' + parameters&#x5B;'dependentproperty'];

    	// get the target value (as a string, 
    	// as that's what actual value will be)
    	var targetvalue = parameters&#x5B;'targetvalue'];
    	targetvalue =
          (targetvalue == null ? '' : targetvalue).toString();

    	// get the actual value of the target control
    	// note - this probably needs to cater for more 
    	// control types, e.g. radios
    	var control = $(id);
    	var controltype = control.attr('type');
    	var actualvalue =
            controltype === 'checkbox' ?
            control.attr('checked').toString() :
            control.val();

    	// if the condition is true, reuse the existing 
    	// required field validator functionality
    	if (targetvalue.toLowerCase() === actualvalue.toLowerCase())
    		return $.validator.methods.required.call(
              this, value, element, parameters);

    	return true;
    }
);

$.validator.unobtrusive.adapters.add(
    'requiredif',
    &#x5B;'dependentproperty', 'targetvalue'],
    function (options) {
    	options.rules&#x5B;'requiredif'] = {
    		dependentproperty: options.params&#x5B;'dependentproperty'],
    		targetvalue: options.params&#x5B;'targetvalue']
    	};
    	options.messages&#x5B;'requiredif'] = options.message;
    });
</pre>
<p>And for propertycvalidator:</p>
<pre class="brush: jscript; title: ; notranslate">
$.validator.addMethod('propertycvalidator', function (value, element, parameters) {
			//put your client-side validation logic here
			return true;
		});

		$.validator.unobtrusive.adapters.addBool('propertycvalidator');
</pre>
<p>And, now we are ready to create our model validator:</p>
<pre class="brush: csharp; title: ; notranslate">
		public class MyValidator()
		{
			//Make SomeProperty required. It will be validated on server and client side.
			RuleFor(m =&gt; m.SomeProperty)
				.NotEmpty().WithMessage(&quot;Some Property is required&quot;);
			//SomeOtherProperty will be validated on server side - it is required when PropertyB is true.
			//Client side validation is done by using RequiredIfClientSideValidator and passing dependent property 
			//name to unubtrusive JavaScript validation
			RuleFor(m =&gt; m.SomeOtherProperty)
				.NotNull()
				.WithMessage(&quot;Some other property is required&quot;)
				.When(model =&gt; model.PropertyB)
				.SetValidator(new RequiredIfClientSideValidator(
					&quot;Some other property is required&quot;,
					&quot;PropertyB&quot;,
					true));

			//this will get validated on client side only
			RuleFor(m =&gt; m.PropertyC)				
				.SetValidator(
					new ClientSideValidator(
						&quot;Error message&quot;,
						&quot;propertycvalidator&quot;)); // must be lower case
		}
</pre>
<p>The last part is adding the attribute to your model:<br />
[Validator(typeof(MyValidator))]<br />
public class YourModel<br />
{<br />
}</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/fluentvalidation-client-side-custom-validation/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			</item>
		<item>
		<title>Hosting SignalR on GoDaddy in Full Trust</title>
		<link>https://blog.adamzolo.com/hosting-signalr-godaddy-full-trust/</link>
					<comments>https://blog.adamzolo.com/hosting-signalr-godaddy-full-trust/#comments</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Sun, 22 Sep 2013 13:13:35 +0000</pubDate>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[SecurityException]]></category>
		<category><![CDATA[SignalR]]></category>
		<guid isPermaLink="false">http://eazolo.com/blog/?p=38</guid>

					<description><![CDATA[I had some free time recently and decided to learn a truly wonderful library SignalR. SignalR provides the &#8220;ability to have your server-side code push content to the connected clients as it happens, in real-time.&#8221; It eliminates the extra work of checking browser compatibility with whatever technology you decide to use for pushing the events&#8230;<p><a class="more-link" href="https://blog.adamzolo.com/hosting-signalr-godaddy-full-trust/" title="Continue reading &#8216;Hosting SignalR on GoDaddy in Full Trust&#8217;">Continue reading <span class="meta-nav">&#8594;</span></a></p>]]></description>
										<content:encoded><![CDATA[<p>I had some free time recently and decided to learn a truly wonderful library <a href="http://signalr.net/" title="ASP.NET SignalR" target="_blank">SignalR</a>. SignalR provides the &#8220;ability to have your server-side code push content to the connected clients as it happens, in real-time.&#8221; It eliminates the extra work of checking browser compatibility with whatever technology you decide to use for pushing the events to the client (WebSockets, EventSource, Long Polling, Forever Frame).</p>
<p>I implemented a simple Chat application using <a href="http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr" title="Tutorial: Getting Started with SignalR (C#)" target="_blank">tutorial from Tim Teebken</a>. It worked beautifully on local machine. However, when deployed to GoDaddy my application went down with <code>System.Security.SecurityException</code>. As it turns out, SignalR can run only in Full Trust mode. What I didn&#8217;t realize is that to enable full trust you have to explicitly specify Trust Level in Web.Config:</p>
<pre class="brush: xml; title: ; notranslate">
&lt;configuration&gt;
    &lt;system.web&gt;
        &lt;trust level=&quot;Full&quot; /&gt;
    &lt;/system.web&gt;
&lt;/configuration&gt;
</pre>
<p>Once this line was added to the configuration and deployed, my simple Chat client started working.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/hosting-signalr-godaddy-full-trust/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>MVC 4 and asynchronous Web applications</title>
		<link>https://blog.adamzolo.com/why-mvc4-async/</link>
					<comments>https://blog.adamzolo.com/why-mvc4-async/#respond</comments>
		
		<dc:creator><![CDATA[Adam Zolo]]></dc:creator>
		<pubDate>Fri, 20 Sep 2013 03:01:29 +0000</pubDate>
				<category><![CDATA[Web]]></category>
		<guid isPermaLink="false">http://eazolo.com/blog/?p=34</guid>

					<description><![CDATA[This is a great explanation of async methods and why to use them in Web applications.]]></description>
										<content:encoded><![CDATA[<p>This is a great explanation of async methods and why to use them in Web applications.</p>
<p><video poster="http://ch9files.blob.core.windows.net/ch9/549f/175536ca-9b63-4aeb-9c56-a053ee21549f/Techdays2012NetherlandsCsharp5ASPMVC4Async_Custom.jpg" controls><source src="http://ak.channel9.msdn.com/ch9/549f/175536ca-9b63-4aeb-9c56-a053ee21549f/Techdays2012NetherlandsCsharp5ASPMVC4Async_mid.mp4" type="video/mp4" /><source src="http://ak.channel9.msdn.com/ch9/549f/175536ca-9b63-4aeb-9c56-a053ee21549f/Techdays2012NetherlandsCsharp5ASPMVC4Async.webm" type="video/webm" /></video></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.adamzolo.com/why-mvc4-async/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="http://ak.channel9.msdn.com/ch9/549f/175536ca-9b63-4aeb-9c56-a053ee21549f/Techdays2012NetherlandsCsharp5ASPMVC4Async.webm" length="205735012" type="video/webm" />
<enclosure url="http://ak.channel9.msdn.com/ch9/549f/175536ca-9b63-4aeb-9c56-a053ee21549f/Techdays2012NetherlandsCsharp5ASPMVC4Async_mid.mp4" length="539575892" type="video/mp4" />

			</item>
	</channel>
</rss>
