Skip to content
Browse files

Code now matches end of Episode #9 (Models and Metadata)

  • Loading branch information...
1 parent 0f6efa8 commit 70af4d0d797966be7205021b39f3e2f105b25aa4 @SteveSanderson committed Feb 19, 2010
View
2 JobAds/ExternalAssemblies/DateTimeEnglishParser-Readme.txt
@@ -0,0 +1,2 @@
+Taken from http://www.codeplex.com/DateTimeEnglishParse
+License: MS-PL
View
BIN JobAds/ExternalAssemblies/DateTimeEnglishParser.dll
Binary file not shown.
View
10 JobAds/JobAds.Domain/Entities/JobAd.cs
@@ -2,20 +2,24 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.ComponentModel;
+using System.Web.Mvc;
namespace JobAds.Domain.Entities
{
- public class JobAd
+ public partial class JobAd
{
- public int JobAdId { get; internal set; }
+ public int JobAdId { get; set; }
public string Title { get; set; }
public Decimal Salary { get; set; }
public string Location { get; set; }
public bool OnlyMembersMaySeeDetails { get; set; }
+
+ public DateTime PublishFromDate { get; set; }
public string MoreDetailsPDFFilename
{
- get { return "c:\\examplePdfDocument.pdf"; } // Sufficient for demo
+ get { return "c:\\examplePdfDocument.pdf"; } // Sufficient for now
}
}
}
View
31 JobAds/JobAds.Domain/Entities/JobAdMetadata.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel;
+using JobAds.Domain.Metadata;
+
+namespace JobAds.Domain.Entities
+{
+ [MetadataType(typeof(JobAdMetadata))]
+ public partial class JobAd
+ {
+ // Not used except as a source of metadata
+ class JobAdMetadata
+ {
+ public int JobAdId { get; internal set; }
+
+ public string Title { get; set; }
+
+ public Decimal Salary { get; set; }
+ public string Location { get; set; }
+
+ [Option(Value=true, DisplayText="Members Only")]
+ [Option(Value=false, DisplayText="Everyone")]
+ public bool OnlyMembersMaySeeDetails { get; set; }
+
+ public DateTime PublishFromDate { get; set; }
+ }
+ }
+}
View
6 JobAds/JobAds.Domain/JobAds.Domain.csproj
@@ -32,13 +32,17 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
+ <Reference Include="System.ComponentModel.DataAnnotations">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
<Reference Include="System.configuration" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
+ <Reference Include="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
@@ -51,6 +55,8 @@
<ItemGroup>
<Compile Include="Abstractions\ISearchSuggestor.cs" />
<Compile Include="Entities\JobAd.cs" />
+ <Compile Include="Entities\JobAdMetadata.cs" />
+ <Compile Include="Metadata\OptionAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Abstractions\IJobAdRepository.cs" />
<Compile Include="Repositories\DbJobAdRepository.cs" />
View
22 JobAds/JobAds.Domain/Metadata/OptionAttribute.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace JobAds.Domain.Metadata
+{
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
+ public class OptionAttribute : Attribute
+ {
+ public string DisplayText { get; set; }
+ public object Value { get; set; }
+
+ public override object TypeId
+ {
+ get
+ {
+ return this;
+ }
+ }
+ }
+}
View
17 JobAds/JobAds/Content/Styles.css
@@ -32,4 +32,19 @@ a.rssLink img, a.excelLink img { border: 0; vertical-align: middle; margin-right
#tempDataMessage { background-color: Red; color: White; font-weight: bold; padding: 0.5em 1em 0.5em 1em; margin-bottom: 0.5em; -moz-border-radius: 0.8em; }
-span.suggestion { margin-left: 2em; }
+span.suggestion { margin-left: 2em; }
+
+/* -- Data entry styles -- */
+div.editor-label { float:left; min-width: 50%; text-align:right; padding-top:0.6em; }
+div.editor-label:after { content: ":" }
+div.editor-field { border: 1px solid white; text-align:left; padding-bottom:0.5em; padding-top: 0.3em; }
+input.text-box { background-image: url(gradient.png); border: 1px solid gray; padding-left: 0.2em; }
+input.check-box { margin-top: 0.5em; }
+input[type=submit] { padding: 0 2em 0 2em; }
+form { text-align: center; }
+.field { text-align:left; padding: 0.5em; border-left: 3px solid orange; margin-bottom: 3px; min-width: 18em; height:100%; }
+.field > label { font-weight: bold; display: block; padding-left: 0.2em; }
+.field .hint { color: Gray; font-size: 0.8em; font-style: italic; }
+.subfield > label { font-weight: normal; float:left; width: 7em; text-align:right; padding-bottom: 0.5em; margin-right: 0.5em; }
+.subfield { margin-top: 0.5em; }
+.fullwidth { width: 100%; }
View
BIN JobAds/JobAds/Content/gradient.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
29 JobAds/JobAds/Controllers/AdminController.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using JobAds.Domain.Entities;
+using JobAds.Utils;
+
+namespace JobAds.Controllers
+{
+ public class AdminController : Controller
+ {
+ public ActionResult Create()
+ {
+ var initialState = new JobAd
+ {
+ PublishFromDate = DateTime.Now.Date
+ };
+ return View(initialState);
+ }
+
+ [HttpPost]
+ public ViewResult Create(JobAd jobAd, string referer)
+ {
+ ViewData["referer"] = referer;
+ return View("BindingResults", jobAd);
+ }
+ }
+}
View
5 JobAds/JobAds/Global.asax.cs
@@ -4,6 +4,7 @@
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
+using JobAds.Utils;
namespace JobAds
{
@@ -30,6 +31,10 @@ protected void Application_Start()
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
+ ModelMetadataProviders.Current = new JobAdsMetadataProvider();
+
+ ValueProviderFactories.Factories.Add(new HttpHeadersValueProviderFactory());
+ ModelBinders.Binders.Add(typeof(DateTime), new NaturalDatesModelBinder());
}
}
}
View
12 JobAds/JobAds/JobAds.csproj
@@ -31,6 +31,10 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="DateTimeEnglishParser, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c5a8a7c192b7fbd1, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\ExternalAssemblies\DateTimeEnglishParser.dll</HintPath>
+ </Reference>
<Reference Include="Ninject.Core, Version=1.0.0.82, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\ExternalAssemblies\Ninject.Core.dll</HintPath>
@@ -68,6 +72,7 @@
<Compile Include="Controllers\AccountController.cs">
<SubType>Code</SubType>
</Compile>
+ <Compile Include="Controllers\AdminController.cs" />
<Compile Include="Controllers\DataEntryController.cs" />
<Compile Include="Controllers\JobsController.cs">
<SubType>Code</SubType>
@@ -79,7 +84,10 @@
<Compile Include="Models\SearchResultsViewModel.cs" />
<Compile Include="NinjectControllerFactory.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Utils\HttpHeadersValueProviderFactory.cs" />
<Compile Include="Utils\iPhoneBrowserAttribute.cs" />
+ <Compile Include="Utils\JobAdsMetadataProvider.cs" />
+ <Compile Include="Utils\NaturalDatesModelBinder.cs" />
<Compile Include="Utils\RssUtils.cs" />
</ItemGroup>
<ItemGroup>
@@ -95,11 +103,15 @@
</ItemGroup>
<ItemGroup>
<Content Include="Content\excel.jpg" />
+ <Content Include="Content\gradient.png" />
<Content Include="Content\rss.jpg" />
<Content Include="Content\Styles.css" />
+ <Content Include="Views\Admin\BindingResults.aspx" />
+ <Content Include="Views\Admin\Create.aspx" />
<Content Include="Views\DataEntry\Index.aspx" />
<Content Include="Views\Jobs\Index.aspx" />
<Content Include="Views\Jobs\Search.aspx" />
+ <Content Include="Views\Shared\EditorTemplates\RadioButtons.ascx" />
<Content Include="Views\Shared\SearchForm.ascx" />
<Content Include="Views\Shared\Site.Master" />
</ItemGroup>
View
18 JobAds/JobAds/Utils/HttpHeadersValueProviderFactory.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using System.Collections.Specialized;
+using System.Globalization;
+
+namespace JobAds.Utils
+{
+ public class HttpHeadersValueProviderFactory : ValueProviderFactory
+ {
+ public override IValueProvider GetValueProvider(ControllerContext controllerContext)
+ {
+ return new NameValueCollectionValueProvider(controllerContext.HttpContext.Request.Headers, CultureInfo.InvariantCulture);
+ }
+ }
+}
View
34 JobAds/JobAds/Utils/JobAdsMetadataProvider.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using JobAds.Domain.Metadata;
+
+namespace JobAds.Utils
+{
+ public class JobAdsMetadataProvider : DataAnnotationsModelMetadataProvider
+ {
+ protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
+ {
+ var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
+
+ if (modelType == typeof(DateTime)
+ && propertyName != null
+ && propertyName.EndsWith("Date"))
+ {
+ metadata.EditFormatString = "{0:yyyy/MM/dd}";
+ }
+
+ var options = attributes.OfType<OptionAttribute>();
+ if (options.Any())
+ {
+ metadata.AdditionalValues["optionValues"]
+ = options.ToDictionary(x => x.Value, x => x.DisplayText);
+ metadata.TemplateHint = "RadioButtons";
+ }
+
+ return metadata;
+ }
+ }
+}
View
44 JobAds/JobAds/Utils/NaturalDatesModelBinder.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Web.Mvc;
+using DateTimeParser;
+
+namespace JobAds.Utils
+{
+ public class NaturalDatesModelBinder : IModelBinder
+ {
+ public object BindModel(ControllerContext controllerContext,
+ ModelBindingContext bindingContext)
+ {
+ // Ensure there's some incoming data
+ string key = bindingContext.ModelName;
+ var valueProviderResult = bindingContext.ValueProvider.GetValue(key);
+ if (valueProviderResult == null || string.IsNullOrEmpty(valueProviderResult.AttemptedValue))
+ return null;
+
+ // Preserve it in case we have to redisplay the form
+ bindingContext.ModelState.SetModelValue(key, valueProviderResult);
+
+ // Now parse
+ var rawText = ((string[])valueProviderResult.RawValue)[0];
+ if (CanParseText(rawText))
+ return DateTimeEnglishParser.ParseRelative(DateTime.Now, rawText);
+ else
+ {
+ // There was a parsing error
+ bindingContext.ModelState.AddModelError(key, "A valid DateTime is required");
+ return null;
+ }
+ }
+
+ private bool CanParseText(string rawText)
+ {
+ // Not very pretty, but DateTimeEnglishParser provides no other way
+ // to determine whether parsing was successful
+ var sample = new DateTime(2001, 01, 01);
+ return sample != DateTimeEnglishParser.ParseRelative(sample, rawText);
+ }
+ }
+}
View
17 JobAds/JobAds/Views/Admin/BindingResults.aspx
@@ -0,0 +1,17 @@
+<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<JobAds.Domain.Entities.JobAd>" %>
+
+<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
+
+ <h2>You entered:</h2>
+
+ <div>JobAdId: <b><%= Model.JobAdId %></b></div>
+ <div>Title: <b><%= Model.Title %></b></div>
+ <div>Salary: <b><%= Model.Salary %></b></div>
+ <div>Location: <b><%= Model.Location %></b></div>
+ <div>OnlyMembersMaySeeDetails: <b><%= Model.OnlyMembersMaySeeDetails %></b></div>
+ <div>PublishFromDate: <b><%= Model.PublishFromDate %></b></div>
+
+ <hr />
+ You came from: <b><%= ViewData["referer"] ?? "unknown" %></b>
+
+</asp:Content>
View
59 JobAds/JobAds/Views/Admin/Create.aspx
@@ -0,0 +1,59 @@
+<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<JobAds.Domain.Entities.JobAd>" %>
+
+<asp:Content ContentPlaceHolderID="TitleContents" runat="server">
+ Post a new Job Ad
+</asp:Content>
+
+<asp:Content ContentPlaceHolderID="MainContent" runat="server">
+
+ <h2>Post a new Job Ad</h2>
+
+ <% using(Html.BeginForm()) { %>
+ <%= Html.HiddenFor(x => x.JobAdId) %>
+ <table align="center">
+ <tr>
+ <td colspan="2">
+ <div class="field">
+ <label>Job title:</label>
+ <%= Html.TextBoxFor(x => x.Title, new { @class = "text-box fullwidth" }) %>
+ </div>
+ </td>
+ </tr>
+ <tr valign="top">
+ <td>
+ <div class="field">
+ <label>Job details</label>
+ <div class="subfield">
+ <label>Base Salary ($):</label>
+ <%= Html.EditorFor(x => x.Salary) %>
+ </div>
+ <div class="subfield">
+ <label>Location:</label>
+ <%= Html.EditorFor(x => x.Location) %>
+ <div class="hint">Specify city and country</div>
+ </div>
+ </div>
+ </td>
+ <td>
+ <div class="field">
+ <label>Ad preferences</label>
+ <div class="subfield">
+ <label>Publish from:</label>
+ <%= Html.EditorFor(x => x.PublishFromDate) %>
+ </div>
+ <div class="subfield">
+ <label>Visible to:</label>
+ <div><%= Html.EditorFor(x => x.OnlyMembersMaySeeDetails) %></div>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </table>
+ <input type="submit" value="Save" />
+
+
+ <% } %>
+
+</asp:Content>
+
+
View
10 JobAds/JobAds/Views/Shared/EditorTemplates/RadioButtons.ascx
@@ -0,0 +1,10 @@
+<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<bool>" %>
+
+<% var options = ViewData.ModelMetadata.AdditionalValues["optionValues"]; %>
+<% foreach(var option in (IDictionary<object, string>)options) { %>
+ <label>
+ <%= Html.RadioButton("", option.Key, option.Key.Equals(Model)) %>
+ <%= option.Value %>
+ </label>
+ <br />
+<% } %>

0 comments on commit 70af4d0

Please sign in to comment.
Something went wrong with that request. Please try again.