I've created my own model property, which says "ErrorMessage could not be found":
[DataType(DataType.EmailAddress)]
[Required]
[Display(Name = "Email")]
[StringLength(80, ErrorMessage = "Email too large.")]
[RegularExpressionTimeOut(#"^([a-zA-Z0-9._-]+)#(outlook|hotmail|yahoo)\.\w{2,}$", 5),
ErrorMessage = "Invalid email."] // 5 seconds
public string Email { get; set; }
And the class of this property:
public class RegularExpressionTimeOut :
System.ComponentModel.DataAnnotations.ValidationAttribute
{
public System.TimeSpan TimeOut { get; set; }
public string Pattern { get; set; }
//public string ErrorMessage { get; set; }
//new System.TimeSpan(0, 0, 5)
/// <summary>
/// Constructor
/// </summary>
/// <param name="pattern">Regular expression as string</param>
/// <param name="timeOut">Number of seconds</param>
public RegularExpressionTimeOut(string pattern, int timeOut)
{
this.Pattern = pattern;
this.TimeOut = new System.TimeSpan(0, 0, timeOut);
}
public override bool IsValid(object value)
{
//if (!string.IsNullOrEmpty(this.ErrorMessage) && ...)
return (new System.Text.RegularExpressions.Regex(this.Pattern,
System.Text.RegularExpressions.RegexOptions.None, this.TimeOut))
.IsMatch(this.Pattern);
}
}
Then, the question is, how can I show the ErrorMessage?
Updated with the FormatErrorMessage
My web don't show the error message anyway.
public class RegularExpressionTimeOut : System.ComponentModel.DataAnnotations.ValidationAttribute
{
public System.TimeSpan TimeOut { get; set; }
public string Pattern { get; set; }
private const string DefaultErrorMessage = "Invalid email.";
//private string ErrorMessage;
/// <summary>
/// Constructor
/// </summary>
/// <param name="pattern">Regular expression as string</param>
/// <param name="timeOut">Number of seconds</param>
public RegularExpressionTimeOut(string pattern, int timeOut) : base(DefaultErrorMessage)
{
this.Pattern = pattern;
this.TimeOut = new System.TimeSpan(0, 0, timeOut);
//this.ErrorMessage = errorMessage;
}
public override bool IsValid(object value)
{
//if (!string.IsNullOrEmpty(this.ErrorMessage) && ...)
return (new System.Text.RegularExpressions.Regex(this.Pattern, System.Text.RegularExpressions.RegexOptions.None, this.TimeOut)).IsMatch(this.Pattern);
}
public override string FormatErrorMessage(string ErrorMessage)
{
return string.Format(ErrorMessageString, ErrorMessage);
}
}
You RegularExpressionTimeOut constructor does not have a parameter for ErrorMessage. The typical usage is
public class RegularExpressionTimeOut : ValidationAttribute
{
private const string DefaultErrorMessage = "Invalid email";
public RegularExpressionTimeOut(string pattern, int timeOut) : base(DefaultErrorMessage)
and usually an override is added to format a specific message provide in the Attribute where you access values such as the property name (although in your case you don't seem to be needing or wanting that.
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, .....);
}
Side note: your closing parenthesis is not located correctly. It should be
[RegularExpressionTimeOut(#"....", 5, ErrorMessage = "Invalid email.")]
Edit
Your not actually testing the value of the Email property which is posted back (you just testing that the regex pattern matches itself. You need to change the IsValid() method to
public override bool IsValid(object value)
{
string email = (string)value; // cast it to a string for use in the Regex method
return (new Regex(this.Pattern, RegexOptions.None, this.TimeOut)).IsMatch(email);
}
Note also that the regex pattern you have shown should take a few millisecond to test so its not clear why you would need this. In addition, you do not have any associated client side validation (your attribute does not implement ICientValitable and in any case there would be no point since javascript regex does not have a timeout) so the message would only be returned if you return the view when ModelState.IsValid == false. In that case, your message "Invalid email" would be rather confusing to the user and it should probably indicate that it needs to be #outlook or #hotmail etc.
Related
I'm wanting to change a request to be GET instead of POST so users can share the generated URLs.
The current system uses a normal form submit and takes advantage of the automatic serialization between the form submit and the MVC ActionResult for a List of custom objects.
e.g.
<form action="/MyPage">
<input type="hidden" id="MyThings_0__Value" name="MyThings[0].Value">
<input type="hidden" id="MyThings_0__Flag" name="MyThings[0].Flag">
<input type="hidden" id="MyThings_1__Value" name="MyThings[1].Value">
<input type="hidden" id="MyThings_1__Flag" name="MyThings[1].Flag">
</form>
However doing it this way causes the GET string generated to be overly long and complicated. This is bad because the MyThings list can be up to 10 items long.
http://myurl.com/MyPage?MyThings%5B0%5D.Value=ThisIsValue1&MyThings%5B0%5D.Flag=1&MyThings%5B1%5D.Value=ThisIsValue2&MyThings%5B1%5D.Flag=2
I was hoping for the string to appear more user-friendly. Something like:
http://myurl.com/MyPage?MyThings=ThisIsValue1-1,ThisIsValue2-2
Can this be done with custom serialization? And if so, how would I go about implementing it?
My Model and ActionResult:
namespace MyNamespace {
public class MyThing {
public string Value { get; set; }
public int Flag { get; set; }
}
public class Filter {
public string CustomAttribute1 { get; set; }
public string CustomAttribute2 { get; set; }
public string CustomAttribute3 { get; set; }
public List<MyThing> MyThings { get; set; } = new List<MyThing>();
}
public ActionResult MyPage(Filter filter) {
MyModel model = StaticMethod.GetMyModel(filter);
return View(model);
}
}
In the end I decided against using custom URL serialization and used helper methods to convert to string/class backward and forwards within the C# model.
public class Filter {
public string MyThing { get; set; }
public List<MyThingClass> MyThings {
get {
if (this._myThings == null) { // Default to Query string
this._myThings = ToQueryList(this.MyThing);
}
return this._myThings;
}
set {
this.MyThing = ToQueryString(value); // Automatically assign QueryString to serialized QueryItems on set
this._myThings = value;
}
}
private List<MyThingClass> _myThings { get; set; }
public static List<MyThingClass> ToQueryList(string queryString) {
return queryString.Split(',').Select(x => MyThingClass.FromString(x)).ToList();
}
public static string ToQueryString(List<MyThingClass> myThings) {
return string.Join(",", myThings.Select(x => x.ToString()));
}
}
public class MyThingClass {
public string Value { get; set; }
public int Flag { get; set; }
/// <summary>Converts a QueryItem object to a serialized object ready for the QueryString.</summary>
public override string ToString() {
return string.Concat(this.Value, "-", this.Flag);
}
public static MyThingClass FromString(string value) {
var v = value.Split('-');
return new MyThingClass() {
Value = v[0],
Flag = Convert.ToInt32(v[1])
};
}
}
I have an object called Content that inherits from ContentBase.
ContentBase is a basic class with few properties.
Content is entirely empty. It just inherits everything from ContentBase.
public class ContentBase
{
public virtual int Id { get; set; }
public virtual string Application { get; set; }
public virtual string Property1 { get; set; }
public virtual string Property2 { get; set; }
}
public class Content : ContentBase
{
}
using Moq I have this test:
[Test]
public void AreEqual_Test()
{
var c1 = new Content() { CultureCode = "Code", ResourceKey = "key", ResourceType = "type", ResourceValue = "value" };
var c2 = new Content() { CultureCode = "Code", ResourceKey = "key", ResourceType = "type", ResourceValue = "value" };
Assert.AreEqual(c1, c2);
}
Which fails with this message:
Expected: <WebPortal.DomainModels.PresentationModel.Content>
But was: <WebPortal.DomainModels.PresentationModel.Content>
at
NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression, String message, Object[] args)
at WebPortal.DomainModels.Tests.PresentationModel.ContentTests.AreEqual_Test() in ContentTests.cs: line 16
This didn't use to happen when Content didn't inherit from ContentBase but still, I find the error message ridiculous.
The Content class contains this Equals:
public override bool Equals(object obj)
{
var content = (Content)obj;
return this.ResourceKey == content.ResourceKey && this.ResourceType == content.ResourceType && this.CultureCode == content.CultureCode;
}
public override int GetHashCode()
{
return this.Id.GetHashCode() + this.Application.GetHashCode();
}
The failing Assert does NOT trigger the Equals method
Assert.AreEqual(c1, c2);
But the Assert below does trigger the Equals and the tests passes:
Assert.IsTrue(c1.Equals(c2));
Has anyone seen this before?
Have you tried overriding GetHashCode? It is highly recommended to override this method when overriding Equals since when the hashcode of different objects don't match, Equals will never be called.
This could be what's happening when calling
Assert.AreEqual(c1, c2);
As a side note, your Equals implementation doesn't follow the Guarantees of Equals
How to create my custom RegularExpressionValidator that gets the RegularExpression and ErrorMessage from Resource file?
[RegularExpression(#"\d{5}(-\d{4})?",
ErrorMessageResourceType = typeof(Global), ErrorMessageResourceName = "regExpValforPostal_ErrorMessage")]
public string PostalCode { get; set; }
The resource file name is Global :
Global.resx, Global.zh.resx, Global.fr-ca.resx
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class LocalizedRegexAttribute : RegularExpressionAttribute
{
static LocalizedRegexAttribute()
{
// necessary to enable client side validation
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedRegexAttribute), typeof(RegularExpressionAttributeAdapter));
}
public LocalizedRegexAttribute(string _RegularExpression, string _ErrorMessageResourceName, Type _ErrorMessageResourceType)
: base(LoadRegex(_RegularExpression))
{
ErrorMessageResourceType = _ErrorMessageResourceType;
ErrorMessageResourceName = _ErrorMessageResourceName;
}
private static string LoadRegex(string key)
{
var resourceManager = new ResourceManager(typeof(Water.Localization.Resources.Global));
return resourceManager.GetString(key);
}
In your model class you need to pass 3 parameters with the custom data
annotation as follows:
[LocalizedRegex("regExpValforPostal_ValidationExpression", "regExpValforPostal_ErrorMessage", typeof(Global))]
public string PostalCode { get; set; }
if your resources file called Validations.he.resx
and inside it you have both 'RegexExpression' and 'ErrorMessage' you should use this:
UPDATE #1: Option 2 added
Option 1:
public class LocalizedRegexAttribute :RegularExpressionAttribute
{
public LocalizedRegexAttribute () : base(Validations.RegexExpression)
{
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(ValidationStrings.ErrorMessage);
}
}
Option 2:
public class EmailAddressAttribute :ValidationAttribute {
public EmailAddressAttribute()
{
}
public override bool IsValid(object value)
{
Regex regex = new Regex(Validations.RegexExpression);
return regex.IsMatch(value.ToString());
}
public override string FormatErrorMessage(string name)
{
return base.FormatErrorMessage(ValidationStrings.ErrorMessage);
} }
than you will use it like this:
[LocalizedRegexAttribute]
public string PostalCode { get; set; }
I have been wondering if I can add custom web services with the wsdl or asmx file extension via the Research Pane in Microsoft Word 2010. I have searched just about every site that has those services, but no luck finding instructions. Rather than trial and error, I felt more confident if I were to ask someone here.
Basically, what I would like to be able to do is add a site like http://www.ebi.ac.uk/Tools/webservices/wsdl or some other source and be able to send queries via the research pane.
Start by reading this http://msdn.microsoft.com/en-us/library/bb226691(v=office.11).aspx
Then the shortcut bellow (it's not perfect, and the actual search is not implemented, but I hope it helps)
1 Service interface
namespace CustomResearchServiceWCF {
[ServiceContract(Namespace="urn:Microsoft.Search")]
public interface IOfficeResearchService
{
[OperationContract(Action = "urn:Microsoft.Search/Registration")]
string Registration(string regXML);
[OperationContract(Action = "urn:Microsoft.Search/Query")]
string Query(string queryXml);
}
}
2 Implementation
namespace CustomResearchServiceWCF
{
public class OfficeResearchService : IOfficeResearchService
{
public string Registration(string regXML)
{
var providerUpdate = new ProviderUpdate();
var writerSettings = new XmlWriterSettings {OmitXmlDeclaration = true,Indent=true};
var stringWriter = new StringWriter();
var serializer = new XmlSerializer(typeof(ProviderUpdate));
using (var xmlWriter = XmlWriter.Create(stringWriter, writerSettings))
{
serializer.Serialize(xmlWriter, providerUpdate);
}
return stringWriter.ToString();
}
public string Query(string queryXml)
{
throw new NotImplementedException();
}
}}
3 ProviderUpdate, ResearchService and License
namespace CustomResearchServiceWCF
{
public class License
{
[XmlAttribute(AttributeName = "acceptRequired")]
public bool AcceptRequired;
public string LicenseText { get; set; }
public License()
{
LicenseText = "some licensing information";
AcceptRequired = true;
}
}
public class Provider
{
public string Message { get; set; }
public License License { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public string QueryPath { get; set; }
public string RegistrationPath { get; set; }
public string Type { get; set; }
public string AboutPath { get; set; }
[XmlAttribute]
public string Action { get; set; }
[DataMember]
public List<ResearchService> Services;
public Provider()
{
Type = "SOAP";
License = new License();
Services = new List<ResearchService>
{
new ResearchService
{
Id = "{942F685E-0935-42c8-80C5-95DB0D129910}",
Name = "Service",
Description = "Custom Research Service",
Copyright = "All content Copyright (c) 2003",
Display = "ON"
}
};
}
}
[XmlType("Service")]
public class ResearchService
{
/// <summary>
/// The GUID that is used when the Query function is called to differentiate a response from your Research service from a response from another Research service
/// </summary>
public string Id { get; set; }
/// <summary>
/// The name displayed in the Research task pane's Show Results From dropdown
/// </summary>
public string Name { get; set; }
/// <summary>
/// //The description displayed in the Properties dialog box for the service
/// </summary>
public string Description { get; set; }
public string Copyright { get; set; }
//Either On or Off; indicates whether the service should be displayed in the Show Results From dropdown.
public string Display { get; set; }
/// <summary>
/// The category with which the service should be grouped in the Show Results From dropdown and the Research options dialog box. See the Microsoft.Search.Registration.Response schema for a list of all the choices.
/// </summary>
public string Category { get; set; }
public ResearchService()
{
Category = "RESEARCH_GENERAL";
}
}
[XmlRoot(Namespace = "urn:Microsoft.Search.Registration.Response")]
public class ProviderUpdate
{
public string Status { get; set; }
public List<Provider> Providers;
public ProviderUpdate()
{
Status = "SUCCESS";
Providers = new List<Provider>
{
new Provider
{
Message = "Congratulations! You've registered Research Pane Examples!",
Action = "UPDATE",
Id = "{942F685E-0935-42c8-80C5-95DB0D129910}",
Name = "Wiktionary",
QueryPath = "http://services.highbeam.com/office/office.asmx",
RegistrationPath = "http://services.highbeam.com/office/office.asmx",
AboutPath = "http://www.highbeam.com"
}
};
}
}
}
I am running on Castle's trunk, and trying to unit-test a controller-action where validation of my DTO is set up. The controller inherits from SmartDispatcherController. The action and DTO look like:
[AccessibleThrough(Verb.Post)]
public void Register([DataBind(KeyReg, Validate = true)] UserRegisterDto dto)
{
CancelView();
if (HasValidationError(dto))
{
Flash[KeyReg] = dto;
Errors = GetErrorSummary(dto);
RedirectToAction(KeyIndex);
}
else
{
var user = new User { Email = dto.Email };
// TODO: Need to associate User with an Owning Account
membership.AddUser(user, dto.Password);
RedirectToAction(KeyIndex);
}
}
public class UserRegisterDto
{
[ValidateNonEmpty]
[ValidateLength(1, 100)]
[ValidateEmail]
public string Email { get; set; }
[ValidateSameAs("Email")]
public string EmailConfirm { get; set; }
[ValidateNonEmpty]
public string Password { get; set; }
[ValidateSameAs("Password")]
public string PasswordConfirm { get; set; }
// TODO: validate is not empty Guid
[ValidateNonEmpty]
public string OwningAccountIdString { get; set; }
public Guid OwningAccountId
{
get { return new Guid(OwningAccountIdString); }
}
[ValidateLength(0, 40)]
public string FirstName { get; set; }
[ValidateLength(0, 60)]
public string LastName { get; set; }
}
The unit test looks like:
[Fact]
public void Register_ShouldPreventInValidRequest()
{
PrepareController(home, ThorController.KeyPublic, ThorController.KeyHome, HomeController.KeyRegister);
var dto = new UserRegisterDto { Email = "ff" };
home.Register(dto);
Assert.True(Response.WasRedirected);
Assert.Contains("/public/home/index", Response.RedirectedTo);
Assert.NotNull(home.Errors);
}
("home" is my HomeController instance in the test; home.Errors holds a reference to an ErrorSummary which should be put into the Flash when there's a validation error).
I am seeing the debugger think that dto has no validation error; it clearly should have several failures, the way the test runs.
I have read Joey's blog post on this, but it looks like the Castle trunk has moved on since this was written. Can someone shed some light, please?
http://www.candland.net/blog/2008/07/09/WhatsNeededForCastleValidationToWork.aspx would appear to contain an answer.