I have a MVC3 app that uses WCF services for data access. WCF services uses EF4.1 for data access.
I want minimum dependencies between MVC3 app and WCF services, so they don't share any libraries. The only dependency in MVC3 app is the service reference.
To validate entities on save, I defined the operation contracts on WCF services to generate FaultContract defined as below:
[OperationContract]
[FaultContract(typeof(EntityFault))]
void AddAddressEntity(Address entity);
EntityFault is defined as below:
[DataContract(IsReference=true)]
public class EntityFault
{
[DataMember]
public string ErrorMessage { get; set; }
[DataMember]
public virtual ICollection<ValidationErrorMessage> ValidationErrorMessages
{ get; set; }
}
and ValidationErrorMessage is a simple class with two properties, PropertyName and ValidationMessage
I trap DbEntityValidationException as below:
try
{ //....
db.SaveChanges();
}
catch (DbEntityValidationException ex)
{
EntityFault ef = EntityFaultHelper.CreateValidationFault(ex, entity);
throw new FaultException<EntityFault>(ef, ef.ErrorMessage);
}
In my MCV3 app I intercept the fault exception. But how can I display the error messages either in
#Html.ValidationMessageFor(<my specific field>)
or in
#Html.ValidationSummary(...)
section?
If the model fields were annotated, or if the client entity implements IValidatableObject, error msgs are displayed in specified areas.
One idea is to use ViewBag, and define display placeholders for error msgs received from WCF's FaultContract, and set the corresponding ViewPag dinamic properties for received error msgs.
But I'm wondering if there's a better approach.
Thanks
So far I found the following solution
I added for each field in the view a placeholder to display error message as below:
<div class="editor-label">
#Html.LabelFor(model => model.State)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.State)
#ViewBag.StateError
</div>
Then in the controller I have this code
using (var addressClient = new AddressServiceClient(_configName))
{
try
{
addressClient.AddAddressEntity(address);
return RedirectToAction("Index");
}
catch (FaultException<EntityFault> ex)
{
foreach (var err in ex.Detail.ValidationErrorMessages)
{
ViewData.Add(
string.Format("{0}Error", err.PropertyName),
err.ErrorMessage);
}
}
}
And it display the errors right next to fields.
But I'm still wondering if there's a way to use the placeholders
#Html.ValidationMessageFor(model => model.Address1)
or
#Html.ValidationSummary(true)
The reason is, I don't want to have to manually change all the Create / Edit views generated by MVC3 VS helper, I prefer to find a way to reuse those placeholders.
Edit
I found a better solution. It works directly with the view generated by MVC out of the box.
The key code is to set the error messages in ModelState as below
try
{
addressClient.AddAddressEntity(address);
return RedirectToAction("Index");
}
catch (FaultException<EntityFault> ex)
{
foreach (var err in ex.Detail.ValidationErrorMessages)
this.ModelState.AddModelError(err.PropertyName, err.ErrorMessage);
}
The point is in ModelState there is a key for each model field (property), with property name as key. So by adding model error for that property, the error message is displayed in the corresponding place for validation error
Related
I'm using two kinds of validation: Client Side and Server Side on a Blazor Project.
Client side is using DataAnnotations, as usual and DataAnnotationsValidator and is working just fine.
Server Side is using this custom server side validation component:
public sealed class ServerSideValidator : ComponentBase
{
private ValidationMessageStore _messageStore;
[CascadingParameter]
private EditContext CurrentEditContext { get; set; }
protected override void OnInitialized()
{
if (CurrentEditContext == null)
{
throw new InvalidOperationException($"{nameof(ServerSideValidator)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. For example, you can use {nameof(ServerSideValidator)} " +
$"inside an {nameof(EditForm)}.");
}
_messageStore = new ValidationMessageStore(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) => _messageStore.Clear();
CurrentEditContext.OnFieldChanged += (s, e) => _messageStore.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
foreach (var (elementId, errorsForElement) in errors)
{
_messageStore.Add(CurrentEditContext.Field(elementId), errorsForElement);
}
CurrentEditContext.NotifyValidationStateChanged();
}
}
And it's also working fine for "direct" properties of the model.
<ValidationMessage For="#(() => model.Property)"/>
Works great. Textbox is red rounded if it's invalid, after the server validation.
Problem is that properties of child model object are being validated (model is set as invalid) and are displayed on ValidationSummary, but the invalid field is not being marked as that.
<ValidationMessage For="#(() => model.Child.Property )"/>
So this is partially working.
When I'm server side validating the attribute, I'm populating the expected list:
IDictionary<string, List<string>> validationErrors
For direct childs (which works) I'm doing:
validationErrors.Add("fieldName", new List {"Is invalid...."});
For childs of model (which doesn't work) I'm doing:
validationErrors.Add("childName.fieldName", new List {"Is invalid...."});
As you can see, although child property is invalid, and form is invalid, jquery shows it as valid.
How do I need to name that property in order for the validator to display the errors?
You need to use the ObjectGraphDataAnnotationsValidator (if you want to use a custom implementation you can find the sources online).
It's in preview but it works fine.
Add this reference to your project:
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0-rc1.20223.4" />
and use it instead of DataAnnotationsValidator:
<EditForm EditContext="#editContext" OnSubmit="#OnSubmit">
#* replace this => <DataAnnotationsValidator /> *#
<ObjectGraphDataAnnotationsValidator />
<ValidationSummary />
...
I am using ValidationMessage in a razor component to show validation message, like this:
<ValidationMessage For="#(() => ViewModel.CompanyNumber)" />
This generates this HTML code:
<div class="validation-message">The company number field is required.</div>
Is it possible to change the CSS-class? I want to use something else than validation-message. Adding class="myclass" is ignored by the controller. I've also tried with #attributes without success.
With .NET5 they added functionality to customize the validation classes on the actual input-fields (which issue 8695 was about) by way of setting a FieldCssClassProvider to the edit context. But there still seems to be no way of customizing the classes of the ValidationSummary or ValidationMessage components
Snipped directly from the .NET 5 docs
var editContext = new EditContext(model);
editContext.SetFieldCssClassProvider(new MyFieldClassProvider());
...
private class MyFieldClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext,
in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return isValid ? "good field" : "bad field";
}
}
Using this will yield the below html for an invalid input. At least with this we can style the actual input elements. Just not the messages...
<input class="bad field" aria-invalid="">
<div class="validation-message">Identifier too long (16 character limit).</div>
You can change the validation-message class inside the css file app.css inside the wwwroot. Or site.css in in earlier previews.
.validation-message {
color: red;
}
The class is set in ValidationMessage.cs
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
foreach (var message in CurrentEditContext.GetValidationMessages(_fieldIdentifier))
{
builder.OpenElement(0, "div");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", "validation-message");
builder.AddContent(3, message);
builder.CloseElement();
}
}
https://github.com/dotnet/aspnetcore/blob/master/src/Components/Web/src/Forms/ValidationMessage.cs
Why don't you just copy the code for ValidationMessage.cs and write in your own property? There is nothing special about this class except for capturing a Cascading Parameter. Just take this file and make your own with a slightly different name then add:
[Parameter] public string AdditionalClassNames {get;set;}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
foreach (var message in CurrentEditContext.GetValidationMessages(_fieldIdentifier))
{
builder.OpenElement(0, "div");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", string.IsNullOrEmpty(AdditionalClassNames) ? "validation-message" : $"validation-message {AdditionalClassNames}");
builder.AddContent(3, message);
builder.CloseElement();
}
}
https://github.com/dotnet/aspnetcore/blob/master/src/Components/Web/src/Forms/ValidationMessage.cs
EDIT
Even better, it's not sealed! Just use it as a base class for a new version and add what I mentioned above.
It is not possible in ASP.NET Core 3.1. Hopefully, it will be included in next major version, see this feature request:
https://github.com/dotnet/aspnetcore/issues/8695
I am working on a multi lingual website using Umbraco 7.2.4 (.NET MVC 4.5). I have pages for each language nested under home nodes with their own culture:
Home (language selection)
nl-BE
some page
some other page
my form page
fr-BE
some page
some other page
my form page
The form model is decorated with validation attributes that I needed to translate for each language. I found a Github project, Umbraco Validation Attributes that extends decoration attributes to retrieve validation messages from Umbraco dictionary items. It works fine for page content but not validation messages.
The issue
land on nl-BE/form
field labels are shown in dutch (nl-BE)
submit invalid form
validation messages are shown in dutch (nl-BE culture)
browse to fr-BE/form
field labels are shown in french (fr-BE)
submit invalid form
Expected behavior is: validation messages are shown in french (fr-BE culture)
Actual behavior is: messages are still shown in dutch (data-val-required attribute is in dutch in the source of the page)
Investigation to date
This is not a browser cache issue, it is reproducible across separate browsers, even separate computers: whoever is generating the form for the first time will lock the validation message culture. The only way to change the language of the validation messages is to recycle the Application Pool.
I doubt that the Umbraco Validation helper class is the issue here but I'm out of ideas, so any insight is appreciated.
Source code
Model
public class MyFormViewModel : RenderModel
{
public class PersonalDetails
{
[UmbracoDisplayName("FORMS_FIRST_NAME")]
[UmbracoRequired("FORMS_FIELD_REQUIRED_ERROR")]
public String FirstName { get; set; }
}
}
View
#inherits Umbraco.Web.Mvc.UmbracoTemplatePage
var model = new MyFormViewModel();
using (Html.BeginUmbracoForm<MyFormController>("SubmitMyForm", null, new {id = "my-form"}))
{
<h3>#LanguageHelper.GetDictionaryItem("FORMS_HEADER_PERSONAL_DETAILS")</h3>
<div class="field-wrapper">
#Html.LabelFor(m => model.PersonalDetails.FirstName)
<div class="input-wrapper">
#Html.TextBoxFor(m => model.PersonalDetails.FirstName)
#Html.ValidationMessageFor(m => model.PersonalDetails.FirstName)
</div>
</div>
note: I have used the native MVC Html.BeginForm method as well, same results.
Controller
public ActionResult SubmitFranchiseApplication(FranchiseFormViewModel viewModel)
{
if (!ModelState.IsValid)
{
TempData["Message"] = LanguageHelper.GetDictionaryItem("FORMS_VALIDATION_FAILED_MESSAGE");
foreach (ModelState modelState in ViewData.ModelState.Values)
{
foreach (ModelError error in modelState.Errors)
{
TempData["Message"] += "<br/>" + error.ErrorMessage;
}
}
return RedirectToCurrentUmbracoPage();
}
}
LanguageHelper
public class LanguageHelper
{
public static string CurrentCulture
{
get
{
return UmbracoContext.Current.PublishedContentRequest.Culture.ToString();
// I also tried using the thread culture
return System.Threading.Thread.CurrentThread.CurrentCulture.ToString();
}
}
public static string GetDictionaryItem(string key)
{
var value = library.GetDictionaryItem(key);
return string.IsNullOrEmpty(value) ? key : value;
}
}
So I finally found a workaround. In attempt to reduce my app to its simplest form and debug it, I ended up recreating the "UmbracoRequired" decoration attribute. The issue appeared when ErrorMessage was set in the Constructor rather than in the GetValidationRules method. It seems that MVC is caching the result of the constructor rather than invoking it again every time the form is loaded. Adding a dynamic property to the UmbracoRequired class for ErrorMessage also works.
Here's how my custom class looks like in the end.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
AllowMultiple = false)]
internal class LocalisedRequiredAttribute : RequiredAttribute, IClientValidatable
{
private string _dictionaryKey;
public LocalisedRequiredAttribute(string dictionaryKey)
{
_dictionaryKey = dictionaryKey;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata, ControllerContext context)
{
ErrorMessage = LanguageHelper.GetDictionaryItem(_dictionaryKey); // this needs to be set here in order to refresh the translation every time
yield return new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage, // if you invoke the LanguageHelper here, the result gets cached and you're locked to the current language
ValidationType = "required"
};
}
}
How do you map additional properties of an exception to your custom fault contract when using Enterprise Library 6's Exception Handling Application Block?
This article describes the FaultContractPropertyMapping the same way this one does. If you have a fault contract like so:
[DataContract]
public class SalaryCalculationFault
{
[DataMember]
public Guid FaultID { get; set; }
[DataMember]
public string FaultMessage { get; set; }
}
How do you add another property and map it to the original exception? Lets say I want to show the Stored Procedure name to the client using a new property:
[DataMember]
public string StoredProcedureName { get; set; }
I try editing the mapping shown on page 90 of the "Developer's Guide to Microsoft Enterprise Library-Preview.pdf" which can be found here but it does not seem to work. My new mapping looks like this:
var mappings = new NameValueCollection();
mappings.Add("FaultID", "{Guid}");
mappings.Add("FaultMessage", "{Message}");
mappings.Add("StoredProcedureName", "{Procedure}"); //SqlException has a Procedure property
And here is the policy.
var testPolicy = new List<ExceptionPolicyEntry>
{
{
new ExceptionPolicyEntry(typeof(SqlException),
PostHandlingAction.ThrowNewException,
new IExceptionHandler[]
{
new FaultContractExceptionHandler(typeof(SalaryCalculationFault), mappings)
})
}
};
var policies = new List<ExceptionPolicyDefinition>();
policies.Add(new ExceptionPolicyDefinition(
"TestPolicy", testPolicy));
exManager = new ExceptionManager(policies);
ExceptionPolicy.Reset();
ExceptionPolicy.SetExceptionManager(exManager);
When I do this and catch the FaultException on the client and inspect it, the StoredProcedureName is always empty. Why doesn't it map from the SqlException to the new property in my fault exception?
It turns out you shouldn't actually place the code you expect an exception for inside of the ExceptionManager.Processs() method. I was doing this before:
exManager.Process(() => wimDAL.Execute_NonQueryNoReturn(sc), "TestPolicy");
Insead, just execute the code as normal.
wimDAL.Execute_NonQueryNoReturn(sc);
This does not follow what the "Developer's Guide to Microsoft Enterprise Library-Preview.pdf" says but I guess the documentation is still a work in progress. I hope this helps someone else.
I would like to know how to make use of SharpArch.WcfClient.Castle.WcfSessionFacility as an alternative to the following verbose-ness.
public ActionResult Index() {
IList<TerritoryDto> territories = null;
// WCF service closing advice taken from http://msdn.microsoft.com/en-us/library/aa355056.aspx
// As alternative to this verbose-ness, use the SharpArch.WcfClient.Castle.WcfSessionFacility
// for automatically closing the WCF service.
try {
territories = territoriesWcfService.GetTerritories();
territoriesWcfService.Close();
}
catch (CommunicationException) {
territoriesWcfService.Abort();
}
catch (TimeoutException) {
territoriesWcfService.Abort();
}
catch (Exception) {
territoriesWcfService.Abort();
throw;
}
return View(territories);
}
The above code has been taken from the TerritoriesController class in SharpArchitecture's NorthWind Sample.
Awaiting
Nabeel
You need to:
Make sure that Windsor knows to call ComponentDestroyed for your client proxies, and that your proxies are registered with "ManageWcfSessions" set to true. Since I'm using it in a web application, I register my client proxies to use a Lifestyle of PerWebRequest:
container
.Register(AllTypes.FromThisAssembly()
.BasedOn(typeof(ClientBase<>))
.WithService.DefaultInterface()
.Configure(c => c
.LifeStyle.Is(LifestyleType.PerWebRequest)
.ExtendedProperties(new { ManageWcfSessions = true }))
);
Add the facility:
container.AddFacility<WcfSessionFacility>(WcfSessionFacility.ManageWcfSessionsKey);
Add the HttpModule to enable the PerWebRequest lifestyle (I can't figure out how to get stackoverflow to format this right - you'll get the idea):
add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor"