ASP.NET WebApi with Protocol Buffers - Error handling - asp.net-web-api

Context:
What i have now:
3-tiers app
Client-Server communication
Server: ASP.NET WebApi v1
Client: HttpClient
Serialization - JSON.NET
However,
JSON.NET is slow
JSON.NET is even slower on the first call (i take it this is because of serializer assembly generation on the fly). This is too slow for me - according the requirements i need to optimize the first call as much as possible.
I am considering using protobuf-net instead of JSON.NET. On a simple PoC app it has shown more than twice as fast result, even the first call, especially when i'd pre-generated the assembly for protocol buffers serializer.
So i've implemented MediaTypeFormatter using protobuf-net and everything works well except one thing - serializing errors.
This is how the exceptions are passed to the client:
public class ExceptionShielderAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, context.Exception);
}
}
Internally, CreateErrorResponse method creates an instance of HttpError (which inherits from Dictionary[string, object]) and writes it to the content.
By default, protobuf-net knows nothing about HttpError so I've tried to add HttpError to protobuf runtime model as follows
typeModel.Add(typeof (HttpError), true);
but it did not help, when i call
typeModel.Compile("ModelSerializer", "ModelSerializer.dll")
it throws InvalidOperationException: No serializer defined for type: System.Object.
Likely due to type of Dictionary[string, object] which is not supported by protobuf-net.
Questions:
Is there anything I can do to serialize errors properly or should i avoid using out-of-the-box errorhanding and implement my own error handling on the server that uses well-know types that protobuf is aware of?
Is protobuf a good choice for my problem at all?

Here is a snippet that enable serialization of System.Web.Http.HttpError using protobuf-net
namespace WS
using System;
using System.Runtime.Serialization;
using System.Web.Http;
using WebApiContrib.Formatting;
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.Add(new ProtoBufFormatter());
ProtoBufFormatter.Model.Add(typeof(HttpErrorProto), true);
var model = ProtoBufFormatter.Model.Add(typeof(HttpError), false);
model.IgnoreListHandling = true;
model.SetSurrogate(typeof(HttpErrorProto));
}
}
[DataContract]
public class HttpErrorProto
{
[DataMember(Order = 1)]
public String ExceptionMessage { get; set; }
[DataMember(Order = 2)]
public String ExceptionType { get; set; }
[DataMember(Order = 3)]
public String InnerException { get; set; }
[DataMember(Order = 4)]
public String MessageDetail { get; set; }
[DataMember(Order = 5)]
public String Message { get; set; }
[DataMember(Order = 6)]
public String ModelState { get; set; }
[DataMember(Order = 7)]
public String StackTrace { get; set; }
public static implicit operator HttpErrorProto(HttpError error)
{
return error == null ? null : new HttpErrorProto
{
ExceptionMessage = error.ContainsKey("ExceptionMessage") ? error["ExceptionMessage"] as string : null,
ExceptionType = error.ContainsKey("ExceptionType") ? error["ExceptionType"] as string : null,
InnerException = error.ContainsKey("InnerException") ? error["InnerException"] as string : null,
MessageDetail = error.ContainsKey("MessageDetail") ? error["MessageDetail"] as string : null,
Message = error.Message,
ModelState = error.ContainsKey("ModelState") ? error["ModelState"] as string : null,
StackTrace = error.ContainsKey("StackTrace") ? error["StackTrace"] as string : null
};
}
public static implicit operator HttpError(HttpErrorProto error)
{
return error == null ? null : new HttpError
{
Message = error.Message
...
};
}
}
}

Your best bet would be to use a surrogate; something like:
typeModel.Add(typeof(HttpError), false)
.SetSurrogate(typeof(MyError));
Where MyError is a custom type of yours which:
has a conversion operator (implicit or explicit) to/from HttpError
is suitable for use from protobuf-net

I've managed work around the problem by adding a special case to my protobuf media type formatter:
public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
{
var completionSource = new TaskCompletionSource<object>();
try
{
if (type == typeof(HttpError))
{
value = CreateDto((HttpError) value);
}
model.Value.Serialize(stream, value);
completionSource.SetResult((object)null);
}
catch (Exception ex)
{
completionSource.SetException(ex);
}
return (Task)completionSource.Task;
}
private HttpErrorDto CreateDto(HttpError error)
{
if (error == null) return null;
return new HttpErrorDto
{
Message = error.GetValueOrDefault<string>("Message"),
ExceptionMessage = error.GetValueOrDefault<string>("ExceptionMessage"),
StackTrace = error.GetValueOrDefault<string>("StackTrace"),
ExceptionType = error.GetValueOrDefault<string>("ExceptionType"),
InnerException = CreateDto(error.GetValueOrDefault<HttpError>("InnerException"))
};
}

Related

Access custom attributes of .NET class inside custom json converter

In my project, I have written a custom json converter to trim the white-spaces present in the string property.
Here is an example of the typical class we will use,
public class Candidate
{
public string CandidateName { get; set; }
}
Here is my custom json converter
public class StringSanitizingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue , JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
string sanitizedString = (reader.Value as string).Trim();
if (StringSanitizeOptions.HasFlag(StringSanitizeOptions.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var text = (string)value;
if (text == null)
writer.WriteNull();
else
writer.WriteValue(text.Trim());
}
}
With my custom converter I am now able to format the string by trimming any white-spaces present sent to the action methods using my 'Candidate' as one of its parameter.
public void Post(ComplexType complexTypeParameter){
}
Everything worked well so far. I later wanted to enhance this json converter to format the string properties based on the attributes set to the string property in the Candidate class. for example, assume I have written my candidate class like this,
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
}
And if I wanted to format the string properties of a class based on the custom attribute configuration inside the json converter , I am not able to access this custom attribute and its configuration inside the ReadJson method of the custom converter.
Here is what I have tried so far but with no luck,
Not present in the CustomAttributes property of the objectType
parameter sent to the ReadJson() method.
Was trying to see if I could extract the parent class of the property inside the ReadJson() method, so that I could apply reflection on the class to extract the custom attributes given to any of its property,but I could not extract that too.
The stack of containing object(s) is not made available to JsonConverter.ReadJson(), thus you cannot do what you want inside ReadJson().
Instead, what you can do is to create a custom contract resolver that applies an appropriately configured instance of StringSanitizingConverter based on the properties of the object for which a contract is being generated.
First, let's say your data model, attribute, and JsonConverter look like the following (where I had to modify a few things to make your code compile and include some additional test cases):
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
[StringSanitizingOptions(Option.DoNotTrim)]
public string StringLiteral { get; set; }
public string DefaultString { get; set; }
public List<string> DefaultStrings { get; set; }
}
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class StringSanitizingOptionsAttribute : System.Attribute
{
public Option StringSanitizeOptions { get; set; }
public StringSanitizingOptionsAttribute(Option stringSanitizeOptions)
{
this.StringSanitizeOptions = stringSanitizeOptions;
}
}
[Flags]
public enum Option
{
Default = 0,
ToLowerCase = (1<<0),
DoNotTrim = (1<<1),
}
public static class StringSanitizeOptionsExtensions
{
public static bool HasFlag(this Option options, Option flag)
{
return (options & flag) == flag;
}
}
public class StringSanitizingConverter : JsonConverter
{
readonly Option options;
public StringSanitizingConverter() : this(Option.Default) { }
public StringSanitizingConverter(Option options)
{
this.options = options;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
var sanitizedString = (reader.Value as string);
if (!options.HasFlag(Option.DoNotTrim))
sanitizedString = sanitizedString.Trim();
if (options.HasFlag(Option.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is never called with null
var text = (string)value;
if (!options.HasFlag(Option.DoNotTrim))
text = text.Trim();
writer.WriteValue(text);
}
}
Next, grab ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net, and define the extension method JsonContractExtensions.AddStringConverters():
public static class JsonContractExtensions
{
public static JsonContract AddStringConverters(this JsonContract contract)
{
if (contract is JsonPrimitiveContract)
{
if (contract.UnderlyingType == typeof(string))
contract.Converter = new StringSanitizingConverter();
}
else if (contract is JsonObjectContract)
{
var objectContract = (JsonObjectContract)contract;
foreach (var property in objectContract.Properties)
{
if (property.PropertyType == typeof(string))
{
var attr = property.AttributeProvider.GetAttributes(typeof(StringSanitizingOptionsAttribute), true)
.Cast<StringSanitizingOptionsAttribute>()
.SingleOrDefault();
if (attr != null)
{
property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
}
}
}
}
return contract;
}
}
public class ConfigurableContractResolver : DefaultContractResolver
{
// This contract resolver taken from the answer to
// https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
// https://stackoverflow.com/a/46083201/3744182
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;
void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}
public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}
public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }
public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}
public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}
Then, finally you can deserialize and serialize Candidate as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
}.Configure((s, e) => { e.Contract.AddStringConverters(); }),
};
var candidate = JsonConvert.DeserializeObject<Candidate>(json, settings);
var json2 = JsonConvert.SerializeObject(candidate, Formatting.Indented, settings);
Notes:
I don't know why the stack of containing object(s) is not available in ReadJson(). Possibilities include:
Simplicity.
A JSON object is "an unordered set of name/value pairs", so trying to access the containing .Net object while reading a property value isn't guaranteed to work, since the information required might not have been read in yet (and the parent might not even have been constructed).
Because a default instance of StringSanitizingConverter is applied to the contract generated for string itself, it is not necessary to add the converter to JsonSerializer.SettingsConverters. This in turn may lead to a small performance enhancement as CanConvert will no longer get called.
JsonProperty.MemberConverter was recently marked obsolete in Json.NET 11.0.1 but must be set to the same value as JsonProperty.Converter in previous versions of Json.NET. If you are using 11.0.1 or a more recent version you should be able to remove the setting.
You may want to cache the contract resolver for best performance.
To modify JsonSerializerSettings in asp.net-web-api, see JsonSerializerSettings and Asp.Net Core, Web API: Configure JSON serializer settings on action or controller level, How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? or ASP.NET Core API JSON serializersettings per request, depending on your requirements and the version of the framework in use.
Sample working .Net fiddle here.

Custom error response in Web Api

On an Web API controller I have the following:
if (!ModelState.IsValid)
return BadRequest();
BadRequest: https://msdn.microsoft.com/en-us/library/system.web.http.apicontroller_methods%28v=vs.118%29.aspx
I would like to create a custom error message to pass errors as json.
Something like:
if (!ModelState.IsValid)
return ModelHasErrors(errors);
How can I create a custom error response?
The simplest way is
return Request.CreateErrorResponse(HttpStatusCode.NotFound, ModelState);
I am used to use special object, that represents error response. I can add my specific (translated) message, status code, etc.
public class ErrorModel
{
public ErrorModel(HttpStatusCode statusCode, string message)
{
StatusCode = (int)statusCode;
Message = message;
ValidationErrors = new Dictionary<string, ModelErrorCollection>();
}
public ErrorModel(HttpStatusCode statusCode)
{
StatusCode = (int)statusCode;
ValidationErrors = new Dictionary<string, ModelErrorCollection>();
}
public string Message { get; set; }
public int StatusCode { get; set; }
public Dictionary<string, ModelErrorCollection> ValidationErrors { get; set; }
public Exception Exception { get; set; }
}
Then I have extension for CreateCustomResponse
public static class ApiExtensions
{
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request, HttpStatusCode statusCode, string errorMessage)
{
var errorMessageModel = new ErrorModel(statusCode, errorMessage);
return request.CreateResponse(statusCode, errorMessageModel);
}
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request, HttpStatusCode statusCode, Exception exception, string errorMessage = "")
{
if (string.IsNullOrEmpty(errorMessage) && exception != null)
{
errorMessage = exception.Message;
}
var errorMessageModel = new ErrorModel(statusCode, errorMessage)
{
Exception = exception
};
return request.CreateResponse(statusCode, errorMessageModel);
}
public static HttpResponseMessage CreateCustomResponse(this HttpRequestMessage request,
HttpStatusCode statusCode, ModelStateDictionary modelState, string errorMessage = "")
{
if (string.IsNullOrEmpty(errorMessage))
{
errorMessage = ApiValidationMessages.GeneralModelIsNotValid;
}
var errorMessageModel = new ErrorModel(statusCode, errorMessage);
foreach (var error in modelState.Where(x => x.Value.Errors != null && x.Value.Errors.Any()))
{
errorMessageModel.ValidationErrors.Add(error.Key.Replace("model.", ""), error.Value.Errors);
}
return request.CreateResponse(statusCode, errorMessageModel);
}
}
And finally in my controllers I just call:
return Request.CreateCustomResponse(HttpStatusCode.NotFound, ApiHttpResultMessages.NotFound);
You can find inspiration in my CodePlex project Web API Design: https://webapidesign.codeplex.com/
You can return directly whichever object you want, and it will be serialized as JSON. It can even be an anonymous class object created with new { }
On The client side you have to check if you've received a regurlar response, or the error object, which can be easyly donde by checking the existence of some property of your custom error object.
You could return a HttpResponseMessage using an object containing your error messages (in this example errors) as content:
return new HttpResponseMessage(HttpStatusCode.BadRequest)
{
Content = new ObjectContent(typeof(ErrorClass), errors, new JsonMediaTypeFormatter())
};
More information about how to return action results can be found here.
You can obviously reuse the above code by creating a method and call it like in your example: ModelHasErrors(errors).
However, if you often find yourself returning the same response, a nice option would be to create an custom exception filter which would return the same response whenever ModelState.IsValid is false.

XML Does not work for me in WebApi -Something to do with my Datacontracts

I am making a web api project and I noticed that one of my method dies when I try to use xml. I usually just use json when testing as this is my preferred method and it works fine.
public HttpResponseMessage GetStoreNames(StoreDm vm)
{
if (ModelState.IsValid)
{
var result = storeService.AutoCompleteStore(vm.Latitude, vm.Longitude, vm.Name);
return Request.CreateResponse <ResponseResult<AutoCompleteSearchDto>>(result.Status.Code, result);
}
var responseResult = new ResponseResultWrapper();
responseResult.Status.Code = HttpStatusCode.BadRequest;
responseResult.Status.Message = GenericErrors.InvalidRequest;
responseResult.ModelStateToResponseResult(ModelState);
return Request.CreateResponse<ResponseResult>(responseResult.Status.Code, responseResult);
}
[DataContract(Name = "MyRoot")]
public class ResponseResultWrapper : ResponseResult
{
public void ModelStateToResponseResult(ModelStateDictionary modelState)
{
foreach (var kvp in modelState)
{
foreach (var error in kvp.Value.Errors)
{
string key = kvp.Key;
string msg = error.ErrorMessage;
if (String.IsNullOrEmpty(msg))
{
msg = error.Exception.Message;
}
base.AddError(key, msg);
}
}
}
}
[DataContract(Name = "MyRoot")]
public class ResponseResult
{
public ResponseResult()
{
Errors = new Dictionary<string, string>();
Status = new ResponseBase();
}
public void AddError(string key, string errorMessage)
{
if (!Errors.ContainsKey(key))
{
Errors.Add(key, errorMessage);
}
}
public bool IsValid()
{
if (Errors.Count > 0)
{
return false;
}
return true;
}
[DataMember]
public Dictionary<string, string> Errors { get; private set; }
[DataMember]
public ResponseBase Status { get; set; }
}
[DataContract(Name = "MyRoot")]
public class ResponseResult<T> : ResponseResult
{
[DataMember]
public T Response { get; set; }
}
I get this error
HTTP/1.1 500 Internal Server Error
Cache-Control: private
Content-Type: application/xml; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcVXNlcnNcV2luZG93czdcRGVza3RvcFxQcmljZUNoZWNrXHRydW5rXFByaWNlQ2hlY2tcUHJpY2VDaGVjay5BcGlcYXBpXHN0b3JlXEdldFN0b3JlTmFtZXM=?=
X-Powered-By: ASP.NET
Date: Thu, 23 May 2013 16:44:14 GMT
Content-Length: 2195
<Error><Message>An error has occurred.</Message><ExceptionMessage>The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.</ExceptionMessage><ExceptionType>System.InvalidOperationException</ExceptionType><StackTrace /><InnerException><Message>An error has occurred.</Message><ExceptionMessage>Type 'ResponseResultWrapper' with data contract name 'http://schemas.datacontract.org/2004/07/Api.Models' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.</ExceptionMessage><ExceptionType>System.Runtime.Serialization.SerializationException</ExceptionType><StackTrace> at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeAndVerifyType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, Boolean verifyKnownType, RuntimeTypeHandle declaredTypeHandle, Type declaredType)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithXsiTypeAtTopLevel(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle originalDeclaredTypeHandle, Type graphType)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractResolver)
at System.Runtime.Serialization.DataContractSerializer.WriteObject(XmlWriter writer, Object graph)
at System.Net.Http.Formatting.XmlMediaTypeFormatter.<>c__DisplayClass7.<WriteToStreamAsync>b__6()
at System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token)</StackTrace></InnerException></Error>
Edit
[DataContract]
public class StoreDm
{
[DataMember(IsRequired = true)]
[MinLength(3)]
public string Name { get; set; }
[DataMember(IsRequired = true)]
public double Latitude { get; set; }
[DataMember(IsRequired = true)]
public double Longitude { get; set; }
}
I get this error after trying "Youssef Moussaoui" first part of his answer.
There was an error deserializing the object of type StoreDm. The data at the root level is invalid. Line 1, position 1.
I can't really do his second part as "ResponseResultWrapper" and "ResponeResult" are in 2 different libraries.
Try changing:
return Request.CreateResponse<ResponseResult>(responseResult.Status.Code, responseResult);
to
return Request.CreateResponse<ResponseResultWrapper>(responseResult.Status.Code, responseResult);
The problem is that DataContractSerializer needs known types to support inheritance. You can learn more about that here:
http://blogs.msdn.com/b/youssefm/archive/2009/04/21/understanding-known-types.aspx
Changing the generic parameter on CreateResponse is the simplest solution, but if you want to serialize derived instances of ResponseResult, you'd want to add known type attributes to ResponseResult like this:
[DataContract(Name = "MyRoot")]
[KnownType(typeof(ResponseResultWrapper))]
public class ResponseResult
{
}
.

How to return Faults in JSON from AJAX Enabled WCF Service?

I have an AJAX-enabled WCF service (with enableWebScript in the behavior) that has a ValidationFault which I created.
Here's the service:
[ServiceContract]
public interface ICoreWCF
{
/// <summary>
/// Saves the Customer.
/// </summary>
[OperationContract]
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
[FaultContract(typeof(ValidationFault))]
void Customer_Save(Customer customer);
}
Here's the fault:
[DataContract]
public class ValidationFault
{
[DataMember(Name = "success")]
public bool Success { get; set; }
[DataMember(Name = "msg")]
public string ValidationMessage { get; set; }
[DataMember(Name = "errors")]
public Dictionary<string, string> Errors { get; set; }
}
I would like to send this fault back to the client javascript.
The problem is that my custom fault's DataMembers are ignored and a general exception is returned.
How can I send the errors collection to the client?
I already tried writing my own IErrorHandler similar to this, such that it uses Exception Handling Application Block to convert an exception to a fault, and then the IErrorHandler serializes the resulting fault. But it appears that the JsonErrorHandler of the WebScriptingEnablingBehavior is not dealing well with the resulting Message object.
Thanks.
If you have implemented IErrorHandler and associated it to service using using custom behavior inherited from WebHttpBehavior as sighted by link then perhaps you should try adding default request/response format etc. For example,
private class CustomWebScriptBehavior : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint,
System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
// clear current error handlers
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
// add our error handler
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(
new ErrorHandler(true));
}
private WebMessageFormat _requestFormat;
private WebMessageFormat _responseFormat;
public CustomWebScriptBehavior()
{
_requestFormat = _responseFormat = WebMessageFormat.Json;
}
public override bool AutomaticFormatSelectionEnabled
{
get { return false; }
set { throw new NotSupportedException(); }
}
public override WebMessageBodyStyle DefaultBodyStyle
{
get { return WebMessageBodyStyle.WrappedRequest; }
set { throw new NotSupportedException(); }
}
public override WebMessageFormat DefaultOutgoingRequestFormat
{
get { return _requestFormat; }
set { _requestFormat = value; }
}
public override WebMessageFormat DefaultOutgoingResponseFormat
{
get { return _responseFormat; }
set { _responseFormat = value; }
}
}
This will eliminate the need to specify WebInvoke attribute for each method.
in webinvoke you can add RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json
try it

MVC3 Validation with Lightspeed

My ORM (LightSpeed) generates this for Animals table, with Name and Age. Using MVC3 and Razor
[Serializable]
[System.CodeDom.Compiler.GeneratedCode("LightSpeedModelGenerator", "1.0.0.0")]
[System.ComponentModel.DataObject]
[Table(IdColumnName="AnimalID", IdentityMethod=IdentityMethod.IdentityColumn)]
public partial class Animal : Entity<int>
{
[ValidatePresence]
[ValidateLength(0, 50)]
private string _name;
[ValidateComparison(ComparisonOperator.GreaterThan, 0)]
private int _age;
public const string NameField = "Name";
public const string AgeField = "Age";
[System.Diagnostics.DebuggerNonUserCode]
[Required] // ****I put this in manually to get Name required working
public string Name
{
get { return Get(ref _name, "Name"); }
set { Set(ref _name, value, "Name"); }
}
[System.Diagnostics.DebuggerNonUserCode]
public int Age
{
get { return Get(ref _age, "Age"); }
set { Set(ref _age, value, "Age"); }
}
With [Required] attribute added:
With no [Required] attribute added: (notice LightSpeed strange rendering of validation)
With name filled in:
In images above - the validation at the top is LightSpeed (put into ValidationSummary) and at the side is MVC3 (put into ValidationMessageFor)
Am only using Server Side validation currently.
Question: How do I get LightSpeed validation working well in MVC3?
I think it is something in this area http://www.mindscapehq.com/staff/jeremy/index.php/2009/03/aspnet-mvc-part4/
For the server side validation - you will want to use a custom model binder which emits the errors from LightSpeed validation more precisely rather than the leveraging the DefaultModelBinder behavior. Have a look at either directly using or adapting the EntityModelBinder from the community code library for Mvc
http://www.mindscapehq.com/forums/Thread.aspx?PostID=12051
See link http://www.mindscapehq.com/forums/Thread.aspx?ThreadID=4093
Jeremys answer (Mindscape have great support!)
public class EntityModelBinder2 : DefaultModelBinder
{
public static void Register(Assembly assembly)
{
ModelBinders.Binders.Add(typeof(Entity), new EntityModelBinder2());
foreach (Type type in assembly.GetTypes())
{
if (typeof(Entity).IsAssignableFrom(type))
{
ModelBinders.Binders.Add(type, new EntityModelBinder2());
}
}
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = base.BindModel(controllerContext, bindingContext);
if (typeof(Entity).IsAssignableFrom(bindingContext.ModelType))
{
Entity entity = (Entity)result;
if (!entity.IsValid)
{
foreach (var state in bindingContext.ModelState.Where(s => s.Value.Errors.Count > 0))
{
state.Value.Errors.Clear();
}
foreach (var error in entity.Errors)
{
if (error.ErrorMessage.EndsWith("is invalid")) continue;
bindingContext.ModelState.AddModelError(error.PropertyName ?? "Custom", error.ErrorMessage);
}
}
}
return result;
}
}
and in Global.asax register using:
EntityModelBinder2.Register(typeof(MyEntity).Assembly);
The Register call sets up the model binder to be used for each entity type in your model assembly so modify as required.
You can get client side validation working with Lightspeed nightly builds from 04/04/2011 onwards.
Create a validator provider as follows:
public class LightspeedModelValidatorProvider : DataAnnotationsModelValidatorProvider
{
private string GetDisplayName(string name)
{
return name; // go whatever processing is required, eg decamelise, replace "_" with " " etc
}
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
if(typeof(Entity).IsAssignableFrom(metadata.ContainerType))
{
List<Attribute> newAttributes = new List<Attribute>(attributes);
var attr = DataAnnotationBuilder.GetDataAnnotations(metadata.ContainerType, metadata.PropertyName, GetDisplayName(metadata.PropertyName));
newAttributes.AddRange(attr);
return base.GetValidators(metadata, context, newAttributes);
}
return base.GetValidators(metadata, context, attributes);
}
}
Then in Application_Start() add
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new LightspeedModelValidatorProvider());

Resources