Lokalizace popisek
Pokud je nutné napsat si vlastní provider, je tedy více než vhodné podědit právě z DataAnnotationsModelMetadataProvider třídy.
Takže pokud chceme mít lokalizované názvy, či klíče pro jejich vyhledání, uloženy v databázi, tak nám k tomu stačí nová třída SimplyCleverModelMetadataProvider, která dědí z DataAnnotationsModelMetadataProvider třídy.
Pokud nám výchozí metodata nevráti DisplayName, tak si v databazí vyhledáme název resource klíče a hodnotu do MVC metadata DisplayName si tak určíme sami - navíc se k ní přidá i *, pokud je údaj povinný:
public class SimplyCleverModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
readonly CountryData[] metadataCountries = null;
public SimplyCleverModelMetadataProvider()
{
this.metadataCountries = CountryProvider.GetCountries();
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
// invoke the base method but skip the DisplayAttribute since it will use resource internally
//var attributesWithoutDisplay = attributes.Where(a => a.GetType() != typeof(DisplayAttribute));
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (modelAccessor != null && containerType == typeof(AddressViewModel) && string.IsNullOrEmpty(metadata.DisplayName))
{
object target = modelAccessor.Target;
AddressViewModel address = target.GetType().GetField("container").GetValue(target) as AddressViewModel;
var metadataCountry = metadataCountries.FirstOrDefault(mc => mc.Code.Equals(address.CountryCode));
try
{
var key = "Address" + metadataCountry.AddressFormatInfo.FieldLabels.First(l => l.Key.Equals(propertyName)).Value;
string name = SimplyCleverResources.ResourceManager.GetString(key);
metadata.DisplayName = name;
bool isRequred = metadataCountry.AddressFormatInfo.FieldRules.First(fr => fr.FieldKey.Equals(propertyName)).Rules.Any(r => r.Key.Equals("Required"));
if (isRequred)
metadata.DisplayName += "*";
}
catch
{
}
}
return metadata;
}
}
{
readonly CountryData[] metadataCountries = null;
public SimplyCleverModelMetadataProvider()
{
this.metadataCountries = CountryProvider.GetCountries();
}
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
// invoke the base method but skip the DisplayAttribute since it will use resource internally
//var attributesWithoutDisplay = attributes.Where(a => a.GetType() != typeof(DisplayAttribute));
var metadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
if (modelAccessor != null && containerType == typeof(AddressViewModel) && string.IsNullOrEmpty(metadata.DisplayName))
{
object target = modelAccessor.Target;
AddressViewModel address = target.GetType().GetField("container").GetValue(target) as AddressViewModel;
var metadataCountry = metadataCountries.FirstOrDefault(mc => mc.Code.Equals(address.CountryCode));
try
{
var key = "Address" + metadataCountry.AddressFormatInfo.FieldLabels.First(l => l.Key.Equals(propertyName)).Value;
string name = SimplyCleverResources.ResourceManager.GetString(key);
metadata.DisplayName = name;
bool isRequred = metadataCountry.AddressFormatInfo.FieldRules.First(fr => fr.FieldKey.Equals(propertyName)).Rules.Any(r => r.Key.Equals("Required"));
if (isRequred)
metadata.DisplayName += "*";
}
catch
{
}
}
return metadata;
}
}
Takto se lze použít bez atributu Display a přesto si lokalizovat popisky:
@Html.TextBoxFor(m => m.City)
Vlastní provider si pak nastavím v Global.asax voláním:
ModelMetadataProviders.Current = new SimplyCleverModelMetadataProvider();
Použití vlastního view
V předchozím dílu jsem zmínil, že pro ulehčení práce jsem si adresy roztřídil na několik skupin, každou zemi přiřadil do dané skupiny a pro danou skupinu jsem napsal i odpovídající view, tedy mám v projektu views:
- AddressLayout1.cshtml,
- AddressLayout2.cshtml,
- AddressLayout3.csthtml
A jak MVC pozná, které view má použít? Nepovažuji za správné, aby v metodě controlleru byla nějaké zbytečný kód, který jen komplikuje čitelnost, takže jsem toto chování vyčlenil do atribut. Kód metody kontrolleru je tak jednoduchý:
public class AjaxController : Controller
{
[AddressFormFilter]
[ValidateInput(false)]
public ActionResult SwitchCountry(AddressViewModel address)
{
if (ControllerContext.IsChildAction)
{
// CHECK.
// in case whole page is rendered from AddressPage1, user parent's ViewData (to propagate ModelState)
this.ViewData = ControllerContext.ParentActionViewContext.ViewData;
}
else
ModelState.Clear();
return PartialView(address);
}
}
{
[AddressFormFilter]
[ValidateInput(false)]
public ActionResult SwitchCountry(AddressViewModel address)
{
if (ControllerContext.IsChildAction)
{
// CHECK.
// in case whole page is rendered from AddressPage1, user parent's ViewData (to propagate ModelState)
this.ViewData = ControllerContext.ParentActionViewContext.ViewData;
}
else
ModelState.Clear();
return PartialView(address);
}
}
Daný atribut je stejně jednoduchý, jako třída pro získání metadat:
public class AddressFormFilterAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var metadataCountry = CountryProvider.GetCountries();
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
AddressViewModel address = filterContext.Controller.ViewData.Model as AddressViewModel;
viewResult.ViewName = "~/Views/Contact/Address" + metadataCountry.First(mc => mc.Code.Equals(address.CountryCode)).AddressFormatInfo.FormatKey + ".cshtml";
}
}
{
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
var metadataCountry = CountryProvider.GetCountries();
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
AddressViewModel address = filterContext.Controller.ViewData.Model as AddressViewModel;
viewResult.ViewName = "~/Views/Contact/Address" + metadataCountry.First(mc => mc.Code.Equals(address.CountryCode)).AddressFormatInfo.FormatKey + ".cshtml";
}
}
Protože celá tato série článků je myšlena spíše jako přehlídka možností MVC pro ASP.NET, je výpis zadaných adres řešen trochu jinak - opět existují šablony pro zobrazení jednotlivých typů adres, mám tedy tyto šablony:
@model MvcSimplyCleverPart3.Models.AddressViewModel
@Model.Line1
@if (!string.IsNullOrEmpty(Model.Line2))
{
<br />@Model.Line2
}
@if (!string.IsNullOrEmpty(Model.County))
{
<br />@Model.County
}
<br />
@Model.City @Model.StateCode, @Model.ZipCode
<br />
@ViewHelper.GetCountryName(Model.CountryCode)
@Model.Line1
@if (!string.IsNullOrEmpty(Model.Line2))
{
<br />@Model.Line2
}
@if (!string.IsNullOrEmpty(Model.County))
{
<br />@Model.County
}
<br />
@Model.City @Model.StateCode, @Model.ZipCode
<br />
@ViewHelper.GetCountryName(Model.CountryCode)
nebo:
@model MvcSimplyCleverPart3.Models.AddressViewModel
@Model.Line1
@if (!string.IsNullOrEmpty(Model.Line2))
{
<br />@Model.Line2
}
<br />
@Model.City
<br/>
@Model.ZipCode
<br />
@ViewHelper.GetCountryName(Model.CountryCode)
@Model.Line1
@if (!string.IsNullOrEmpty(Model.Line2))
{
<br />@Model.Line2
}
<br />
@Model.City
<br/>
@Model.ZipCode
<br />
@ViewHelper.GetCountryName(Model.CountryCode)
atd.
Adresy jsou vypisovány v seznamu pomocí tohoto kódu - podobná logika jako v AddressFormFilterAttribute je použita pro nalezení šablony pro každou položku:
@model MvcSimplyCleverPart3.Models.UserAddressViewModel[]
<div class="addresses">
@foreach (var userAddress in Model)
{
<div class="address">
@Html.Partial(GetTemplate(userAddress.Address.CountryCode), userAddress.Address)
</div>
<div class="modified">
<span class="label">@SimplyCleverResources.LastModified</span>@Html.DisplayFor(m=> userAddress.LastModified)
</div>
}
</div>
@functions
{
private string GetTemplate(string countryCode)
{
var metadataCountry = SimplyCleverMiddleTier.CountryProvider.GetCountries();
return "~/Views/Contact/DisplayTemplates/Address" + metadataCountry.First(mc => mc.Code.Equals(countryCode)).AddressFormatInfo.FormatKey + ".cshtml";
}
}
<div class="addresses">
@foreach (var userAddress in Model)
{
<div class="address">
@Html.Partial(GetTemplate(userAddress.Address.CountryCode), userAddress.Address)
</div>
<div class="modified">
<span class="label">@SimplyCleverResources.LastModified</span>@Html.DisplayFor(m=> userAddress.LastModified)
</div>
}
</div>
@functions
{
private string GetTemplate(string countryCode)
{
var metadataCountry = SimplyCleverMiddleTier.CountryProvider.GetCountries();
return "~/Views/Contact/DisplayTemplates/Address" + metadataCountry.First(mc => mc.Code.Equals(countryCode)).AddressFormatInfo.FormatKey + ".cshtml";
}
}
V následujícím příspěvku popíši lokalizaci cest a následně i způsob získání a prosazení validačních pravidel.
Žádné komentáře:
Okomentovat