Finally OData 7.x supports and ASP.NET Core 2.x0 Reacties

Finally we can create OData (v4) services using ASP.NET Core.

My first impression, it’s ok, plus it has extra features.

Use my startup project, on Github as a sample. (It’s tricky to forget to enable features like Filter, otherwise you’ll and up with :

value":"The query specified in the URI is not valid. The property 'Id' cannot be used in the $filter query option."}

So, this is the Configure section

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
       {
           if (env.IsDevelopment())
           {
               app.UseDeveloperExceptionPage();
           }
           var builder = new ODataConventionModelBuilder(app.ApplicationServices);
           builder.EntitySet("Cars");
      
           app.UseMvc(routeBuilder =>
           {
               routeBuilder.Count().Filter().OrderBy().Select().MaxTop(null).Expand();
               routeBuilder.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
               // Work-around for #1175

               routeBuilder.EnableDependencyInjection();
           } ) ;
       }

To get PATCH and POST working, we need the [FromBody] Attribute like here

public async Task Post([FromBody] Car newCar)

 

The sample, is downloadable here (ASP.NET-Core-With-Odata-7)

OData 6.0.0 howto fix Cannot apply PATCH to navigation property 'x' on entity type?0 Reacties

The Microsoft OData library Roadmap? Please some insights?

WP_20140923_005Microsoft has taken the road of open source and the community. Right, but I guess that this is the reason, that some libraries, in the past maintained by strictly managed teams, now are more or less ‘loosely’ managed.

About OData and bugs and questions, I have seen comments in the community, that suggest that Microsoft abandons maintenance on oData, however, I doubt this is the case, since Azure heavily depends on Odata.

For the reason of better versioning and no dependencies on Windows Updates for .NET components, .NET library has been split up, into smaller parts, the future part is ‘.NET Core’ and the current path, but popular as well, is .NET for which we see a mysterious overlap in versions, so say, if you target your assembly, you could target it using Visual Studio 2015 and C# to both as well .NET 462 and .Net Core 1.6 (standard core library). My focus on this posting, is not the .NET versions but the OData versions.

It looks as if OData 5.8.x is still the most popular used OData Service library, while Microsoft guys, also created a System.WEb.Odata 6.0.0 version, which is compatible with the ‘Next’ platform of .NET (say, .NET Core, I am not quite sure what exactly is going on here).

To make it worse, if you develop on Azure services, some clients, such as Azure Search Index Client, use OData 5.8 while DocumentDB already likes 6.0.0, these two odata branches, bite each other.

Anyway, OData 6.0.0 has improved functionality when we talk about modelling, but it has a lot of BUGS in the Controller handling section, that still, since the release of it have not been solved.

So, If you have these libraries, which are from the nuget packages.config file, read on!


For complete details about the bug, I have created an Issue on Github, where the OData WebApi Git Repository resides.

Now the question, how to workaround the BUG?

Good news, you can workaround it, but the workaround requires you to break some consistancy with your OData API behavior. I tried to minimize the damage as much as possible AND if Microsoft fixes these, you just could rename a few things and your OData API Controller, should work on!

Second, I just could post the binaries here, and say: “Good luck with it!” But it also explains a few other things

  1. How to intercept an OData server method by getting binary data and do it yourselves?
  2. How to deal with ‘custom’ serialisation on Odata, see, using Newtonsoft.JSON. (Which is possible, but I do not recommend it for 6.0.0)
  3. How to impress your team members with this neat OData PATCH-patch? Glimlach

 

My Odata Controller layout

Say, this is my Controller. You will recognize it, it’s not very different probably.

[EnableQuery]
[Authorize]
[ODataRoutePrefix("companies")]
public class CompaniesController : BaseODataController

private static readonly ODataValidationSettings settings = new ODataValidationSettings()
       {
           // Initialize settings as needed.
           AllowedFunctions = AllowedFunctions.IndexOf | AllowedFunctions.ToLower | AllowedFunctions.All | AllowedFunctions.Any | AllowedFunctions.AllStringFunctions //includes contains
       };

public CompaniesController(IManager companyManager, // DEAL with IoC, just a sample
       )
    {
        _companyManager = companyManager;
        }

try
           {
               options.Validate(settings);
           }
           catch (Exception ex)
           {
               return BadRequest(ex.Message);
           }
           try
           {
               var data = (await _companyManager.SearchAsync(options));

               return Ok(data);
           }
           catch (Exception ex)
           {
               return BadRequest(ex.Message);
           }

 


       [EnableQuery]
     public async Task Get(ODataQueryOptions options)
     {

try
           {
               options.Validate(settings);
           }
           catch (Exception ex)
           {
               return BadRequest(ex.Message);
           }
           try
           {
               var data = (await _companyManager.SearchAsync(options));

               return Ok(data);
           }
           catch (Exception ex)
           {
               return BadRequest(ex.Message);
           }

}

 

public async Task Delete(Guid key)
     {
         if (!ModelState.IsValid)
         {
             return BadRequest(ModelState);
         }
         var result = await _companyManager.DeleteAsync(key);
         if (!result)
         {
             return BadRequest(_companyManager.GetValidator());
         }
         return this.StatusCode(HttpStatusCode.NoContent);
     }

public async Task Post(company c)
     {
         if (!ModelState.IsValid)
         {
             return BadRequest(ModelState);
         }
         c.id = Guid.NewGuid();

   var success = await _companyManager.InsertAsync(c);

if (!success)
              {
                  return BadRequest(_companyManager.GetValidator());
              }
    return Created(c);

}


// NOW we come to the BUG on Odata 6.0.0. The OData PATCH method would be USEless if you consider the fact, that complex entitytypes canNOT be patched as of the moment of writing.

public async Task Patch(Guid key, [DeltaBody] Data.OData.Delta delta)
     {

         if (delta == null)
         {
             return BadRequest("delta cannot be null");
         }
         var instance = delta.GetEntity();

         Validate(instance);

         if (!ModelState.IsValid)
         {
             return BadRequest(ModelState);
         }
        
         var curCompany = await _companyManager.GetById(key);
         if (curCompany == null)
         {
             return NotFound();
         }
             delta.Patch(curCompany);

           try
         {

             var result = await _companyManager.UpdateAsync(curCompany);
             if (!result)
             {
                 return BadRequest(_companyManager.GetValidator());
             }
             if (WantContent)
             {
                 return Content(HttpStatusCode.Created, curCompany);
             }
             return Updated(curCompany);
         }
         catch (Exception ex)
         {

//yadda
                 }
     }

DeltaBodyAttribute my little Gem!

Rick Strahl on Rick Strahls custom Body filter helped me to customize my PATCH method. Normally, the Delta<> would be like System.Web.OData.Delta. But now we have to tweak this code into some Delta override which is a duplicate code of OData 5.8 where the Delta Patch works.

AS you can see, your PATCH method, only needs this adaption, and later on, you could change it back to the intended OData behavior if Microsoft fixes the library version 6.0.0 or higher.

Keep in mind!

  1. Since we use NewtonSoft serialisation, ODataConventionModelBuilder, is ignored. All configuration, such as how to deal with nullables, capitalisation, complex property serialisation, must be emulated by Newtonsoft. OData has it’s own serialisation fabric, and especially for 6.0.0 the samples simply don’t work, and documentation lacks. (Enough with the rants, for now)
  2. Your model, that normally applies to Odata, must be copied so Newtonsoft works.
  3. I don’t use camelcase/ruby on rails naming conventions and JSonProperty tricks for our frontend HTML consumers, to get my C# naming conventions ‘right’. So my classes (POC) are really like this:  public string first_name {get;set;}
    This is to avoid further complex compatibility code, between OData entitytypes and our ‘patch’ that uses Newtonsoft. 
  4. if you use dynamic_properties, for your entitymodel, which OData supports, they are called Open Type definitions, this might bite
  5. Exceptions, during serialisation, will be Newtonsoft exceptions, not OData exceptions.

If you keep these changes in mind, maybe, you could live with this workaround? Because after all,

DeltaBodyAttribute

using System;
using System.Web.Http;
using System.Web.Http.Controllers;


// see https://github.com/RickStrahl/AspNetWebApiArticle/tree/master/AspNetWebApi

namespace MyMuke.Data.OData
{
    ///


    /// An attribute that captures the entire content body and stores it
    /// into the parameter of type
    ///

    ///
    /// The parameter marked up with this attribute should be the only parameter as it reads the
    /// entire request body and assigns it to that parameter.   
    ///

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
    public sealed class DeltaBodyAttribute : ParameterBindingAttribute
    {
        ///
        ///
        ///

        ///
        ///
        public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
        {
            if (parameter == null)
                throw new ArgumentNullException(nameof(parameter));

            return new DeltaBodyParameterBinding(parameter);
        }
    }
}

DeltaBodyParameterBinding


using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata;

 

namespace MyMuke.Data.OData
{
    ///


    /// Reads the Request body into a Delta<>
    /// assigns it to the parameter bound.
    ///
    /// Should only be used with a single parameter on
    /// a Web API method using the [RawBody] attribute
    ///

    public class DeltaBodyParameterBinding : HttpParameterBinding
    {
        ///
        /// ctor
        ///

        ///
        public DeltaBodyParameterBinding(HttpParameterDescriptor descriptor)
            : base(descriptor)
        {

        }

        ///


        /// Check for simple
        ///

        ///
        ///
        ///
        ///
        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                                                    HttpActionContext actionContext,
                                                    CancellationToken cancellationToken)
        {
 
            if (!actionContext.Request.Method.Method.Equals("PATCH", StringComparison.InvariantCultureIgnoreCase))// PUT ~Patch is considered
                return Task.FromResult(0);

           var binding = actionContext
                .ActionDescriptor
                .ActionBinding;
            var type = binding
                        .ParameterBindings.FirstOrDefault(f => f is DeltaBodyParameterBinding)
                        .Descriptor.ParameterType;

            if (type.IsGenericType && type.GetGenericTypeDefinition().IsAssignableFrom(typeof(Data.OData.Delta<>)))
            {
                return actionContext.Request.Content
                        .ReadAsStreamAsync()
                        .ContinueWith((task) =>
                        {
                            // create instance of e.g. Delta()
                            var delta = (Delta)Activator.CreateInstance(typeof(Data.OData.Delta<>).MakeGenericType(type.GetGenericArguments()));

                            var jOb = default(JToken);

                            using (var mem = new MemoryStream((int)task.Result.Length))
                            {
                                task.Result.CopyTo(mem);
                                mem.Position = 0;
                                var sr = new StreamReader(mem);

                                // deserialize twice
                                // one time as JTOken, the other time to get TEntityType
                                try
                                {
                                    var ser = new JsonSerializer()
                                    {
                                        PreserveReferencesHandling = PreserveReferencesHandling.All,
                                        MissingMemberHandling = MissingMemberHandling.Ignore//you dont want JsonRequired to cause problems here
                                    };
                                   // ser.Converters.Add(new ArrayReferencePreservngConverter()); HERE YOUR OWN solution for self referencing types

                                    jOb = JToken.ReadFrom(new JsonTextReader(sr));
                                }
                                catch (Exception ex)
                                {
                                    var msg = ex.Message;
                                    throw ex;
                                }
                            }
                            JToken token;
                            if ((token = jOb["id"]) != null)//not the primary key can be patched todo check on KeyAttribute
                            {
                                token.Parent.Remove();
                            }
                            DeltaCopyUtil.CopyEntityToDelta(delta, jOb);

                            SetValue(actionContext, delta);
                        });
            }
        

            throw new InvalidOperationException("Only Delta parameters");
        }
        ///


        /// returns blah
        ///

        public override bool WillReadBody
        {
            get
            {
                return true;
            } <

How to convert WCF REST services to Web API 2 (ASP.NET)0 Reacties

 

Why convert WCF REST services anyway?
First: WCF REST processes JSON using (by default) the DataContractJsonSerializer, while Web API 2 (by default) uses NewtonSoft JSON, which today is the best choice.
Secondly: WCF more or less runs in ASP.NET context, using a HttpContext hack plus routing to a WCF service is much more complex than the easy Attributed like (RoutePrefixAttribute) Web API controller.
Third: Arguably, WCF REST services, simply are from the previous generation .NET. Certainly, I still would use WCF for implementing a SOAP client/server, but not for REST.

So, how can I convert my WCF Rest services to Web API 2 without telling customers to implement changes as well?

Here is my experience, which might gain some time for you.

Just this, my customer already used VB.NET in this project, so don’t blame me for using VB.NET instead of C# :)

The existing Services (called ‘Controllers’ in Web API terms) looked like this…

Service Body definition+attributes


https://mydomain.blah.nl/Service")>
     Name:="MobileService",
     ConcurrencyMode:=ConcurrencyMode.Multiple,
     MaxItemsInObjectGraph:=Integer.MaxValue)>

Partial Public Class MobileService
     Inherits ServiceBase
….

ServiceBase is just a base class which has some methods for shortening getting the MemberShip User and Booleans like ‘IsAdmin’. For brevity, you don’t need to see it.

Service actions

They look like this:



Public Function GetProduct(productid As String) As Product
    If MembershipUser Is Nothing Then Throw New UnauthorizedAccessException()
    Return New Product(CInt(productid))
End Function

There are several attributes, such as WebGet etc, which you quickly recognize having counterparts in WebAPI2

WCF REST ?

You know, REST = HTTP [ACTION VERB(S)] URL + [body].
WCF had an Attribute AspNetCompatibilityRequirements which enabled you to even have a Session State and to run within the ASP.NET pipeline. However, REST should not have a ‘session state’.

The response can depending on the Http Header Accept be either application/json, or application/xml

JSON is the easiest stuff, because it does not deal with XML namespaces. However, if an endpoint client requests application/xml, the service might return a constructed rootname element, using the controller name as a basename. Such as  http://www.w3.org/2001/XMLSchema-instance" xmlns=http://schemas.datacontract.org/2004/07/Yadda">

As you see, because my WCF REST controller was named HardwareService, it is being used in the XML output. If you have existing customers, you cannot just modify it to be, say, ‘ArrayOfHardwareController’.

Now, a real service implementor, would advice you to use CollectionDataContract attributes. Please do so, for new from scratch projects, However, again, I don’t want to redefine my existing object model, which can be a lot of work!

TIP 1: Use this excelent hack, which really works like a charm.

http://www.strathweb.com/2013/02/but-i-dont-want-to-call-web-api-controllers-controller/

In VB.NET (I guess, your gasping to see it?) this little helper is like this:

Public Class CustomHttpControllerTypeResolver
      Inherits DefaultHttpControllerTypeResolver
      Public Sub New()
          MyBase.New(Function(T As Type)
                         If T Is Nothing Then Throw New ArgumentNullException("t")
                         Return T.IsClass AndAlso T.IsVisible AndAlso Not T.IsAbstract AndAlso GetType(ApiController).IsAssignableFrom(T) AndAlso GetType(IHttpController).IsAssignableFrom(T)
                     End Function)
      End Sub
  End Class

In Application_OnStart (or so) you add this

Web.Http.GlobalConfiguration.Configuration.Services.Replace(GetType(System.Web.Http.Dispatcher.IHttpControllerTypeResolver), New Api.CustomHttpControllerTypeResolver())

In WebApiConfig use this:

'override the suffix 'Controller' requirement
Dim suffix = GetType(DefaultHttpControllerSelector).GetField("ControllerSuffix", BindingFlags.Static Or BindingFlags.Public)
If suffix IsNot Nothing Then suffix.SetValue(Nothing, String.Empty)
I really like this hack above! Because we don’t need to mess with caching the WebAPI2 controllers ourselves. Which is indeed madness to implement ourselves (mostly).

Now the next challenge. Most companies have JSON/XML services available as service for both end-to-end points, but also as data-source in websites, which consume it using javascript.
In ASP.NET, you probably have some FormsAuthentication mechanism, which is cookie based and optimized for persisting an authenticated session.
WebAPI 2 Controllers do support this, using the Authorize attribute, however, you’ll discover, it does NOT support Basic authentication, which is in combination with SSL, a good candidate for encryption over data for most B-2-B endpoints..

So you need a ‘hack’ to elegantly support BOTH FormsAuthentication, and Basic Authentication. Note, the sample from the Web I used it from, ONLY supports BasicAuthentication, incorrectly calling it ‘Mixed’ support, which it was not. My code however, does support both FormsAuthentication as well as Basic authentication.

Note 1: It does not support the FormsAuthentication challenge sequence, which I don’t need since one normally does not log on using a browser to a JSON Service URL/endpoint. So, MyBase.IsAuthorized(actionContext) does the trick. Thus you don’t have to validate the .aspxauth cookie (Part of FormsAuthentication) yourselves.

Note 2: You must finish the TODO comment, otherwise, the attribute won’t work for you.

TIP 2 Use the attribute below, as a replacement for the Authenticate attribute.

'''

 
''' HTTP authentication filter for ASP.NET Web API
'''

''' http://piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/"/>
Public MustInherit Class BasicHttpAuthorizeAttribute
    Inherits AuthorizeAttribute

    Private Const BasicAuthResponseHeader = "WWW-Authenticate"
    Private Const BasicAuthResponseHeaderValue = "Basic"

    Public Overrides Sub OnAuthorization(actionContext As HttpActionContext)

        If (actionContext Is Nothing) Then
            Throw New ArgumentNullException("actionContext")
        End If
        If (AuthorizationDisabled(actionContext) OrElse MyBase.IsAuthorized(actionContext) OrElse AuthorizeRequest(actionContext.ControllerContext.Request)) Then
            Return
        End If

        HandleUnauthorizedRequest(actionContext)
    End Sub

    Protected Overrides Sub HandleUnauthorizedRequest(actionContext As HttpActionContext)

        If (actionContext Is Nothing) Then
            Throw New ArgumentNullException("actionContext")
        End If
        actionContext.Response = CreateUnauthorizedResponse(actionContext.ControllerContext.Request)
    End Sub

    Private Shared Function CreateUnauthorizedResponse(request As HttpRequestMessage) As HttpResponseMessage

        Dim result = New HttpResponseMessage() With
                     {
                        .StatusCode = HttpStatusCode.Unauthorized,
                        .RequestMessage = request
                    }

        'we need to include WWW-Authenticate header in our response,
        'so our client knows we are using HTTP authentication
        result.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue)
        Return result
    End Function

    Private Shared Function AuthorizationDisabled(actionContext As HttpActionContext) As Boolean
        'support New AllowAnonymousAttribute
        If Not actionContext.ActionDescriptor.GetCustomAttributes(Of AllowAnonymousAttribute).Any() Then
            Return actionContext.ControllerContext.ControllerDescriptor().GetCustomAttributes(Of AllowAnonymousAttribute).Any()
        Else
            Return True
        End If
    End Function

    Private Function AuthorizeRequest(request As HttpRequestMessage) As Boolean

        Dim authValue = request.Headers.Authorization
        If (authValue Is Nothing OrElse String.IsNullOrWhiteSpace(authValue.Parameter) OrElse
            String.IsNullOrWhiteSpace(authValue.Scheme) OrElse
            authValue.Scheme <> BasicAuthResponseHeaderValue) Then

            Return False
        End If

        Dim parsedHeader = ParseAuthorizationHeader(authValue.Parameter)
        If parsedHeader Is Nothing Then
            Return False
        End If
        Dim principal As IPrincipal = Nothing
        If TryCreatePrincipal(parsedHeader(0), parsedHeader(1), principal) Then

            HttpContext.Current.User = principal
            Return CheckRoles(principal) AndAlso CheckUsers(principal)

        Else
            Return False
        End If
    End Function

    Private Function CheckUsers(principal As IPrincipal) As Boolean

        Dim usrs = UsersSplit
        If usrs.Length = 0 Then Return True
        'NOTE: This is a case sensitive comparison
        Return usrs.Any(Function(u) principal.Identity.Name = u)
    End Function

    Private Function CheckRoles(principal As IPrincipal) As Boolean

        Dim rls = RolesSplit
        If rls.Length = 0 Then Return True
        Return rls.Any(Function(r) principal.IsInRole(r))
    End Function

    Private Shared Function ParseAuthorizationHeader(authHeader As String) As String()

        Dim credentials = Encoding.ASCII.GetString(Convert.FromBase64String(authHeader)).Split(":"c)
        If (credentials.Length <> 2 OrElse String.IsNullOrEmpty(credentials(0)) OrElse
            String.IsNullOrEmpty(credentials(1))) Then
            Return Nothing
        End If
        Return credentials
    End Function

    Protected ReadOnly Property RolesSplit() As String()
        Get
            Return SplitStrings(Roles)
        End Get
    End Property

    Protected ReadOnly Property UsersSplit() As String()
        Get
            Return SplitStrings(Users)
        End Get
    End Property

    Protected Shared Function SplitStrings(input As String) As String()
        If String.IsNullOrWhiteSpace(input) Then Return New String() {}
        Dim result = input.Split(","c).Where(Function(s) Not String.IsNullOrWhiteSpace(s.Trim()))
        Return result.Select(Function(s) s.Trim()).ToArray()
    End Function

    '''


    ''' Implement to include authentication logic and create IPrincipal
    '''

    Protected MustOverride Function TryCreatePrincipal(user As String, password As String, ByRef principal As IPrincipal) As Boolean
End Class
Public Class MembershipHttpAuthorizeAttribute
    Inherits BasicHttpAuthorizeAttribute

    '''


    ''' Implement to include authentication logic and create IPrincipal
    '''

    Protected Overrides Function TryCreatePrincipal(user As String, password As String, ByRef principal As IPrincipal) As Boolean

        principal = Nothing
        If Not Membership.ValidateUser(user, password) Then
            Return False
        End If
        Dim rles = Web.Security.Roles.Provider.GetRolesForUser(user)

'TODO: You must assign here your OWN principal       

        'principal = New GenericPrincipal(New GenericIdentity(user), roles)
        Return True
    End Function

End Class

RESULT

Final Controller body


Partial Public Class MobileService
    Inherits Api.ApiBaseController

As you can see, I don’t have the ‘ Controller’  suffix for my Web API2 controller, and I even can use the RoutePrefix attribute. Second, I did not use ‘ Authorize’  attribute, but the mixed MembershipHttpAuthorize attribute.

Controller Actions

'


' Looks up some data by ID.
'


Public Function GetProduct(productid As Integer) As IHttpActionResult

    Return Ok(New Product(productid))
End Function

I don’t know if WCF could support non-string parameters, I don’t want to know, anyway, as above, you see, it’s quite simple.

In this case, I like to have a function of type IHttpActionResult, because than I easily can return BadRequest or NotFound(). http://www.asp.net/web-api/overviewaa

 

Quirks.

- Sometimes, it seemed that JQuery simply did not behave nicely with a REST / JSON call (this also was the case in the WCF implementation of my client), that only returns HTTP 200 (OK) with no return body. So, I found out, that service reliability improved by returning A value such as Ok(True). So, basically, always define your actions with a specific type, not being ‘void’ or ‘sub’. OK?

- Another issue occurring with HttpPost and HttpPut is when parameters are partly from a Uri and partly from body. WCF could figure this out, but strangely enough, you must help Web Api 2 using attributes FromUriAttribute and FromBodyAttribute. I did not have time to figure out when this was needed, or not but added the attribute.

So:


Public Function GetCustomerConsumer(networkid As Integer, req As GetCustomerConsumerRequest) As IHttpActionResult
    Try
        Return Ok(GetCustomer(networkid, req))
    Catch ex As Exception
        Return BadRequest(ex.Message)
    End Try
End Function

In the sample below, it certainly was necessary to define a ‘ dummy’  class, to pass simple types like status, which is an integer.


Public Function SetStatus(myid As Integer, dm As Dummy) As IHttpActionResult

Public Class Dummy
     Public remark As String
     Public status As Integer
End Class

- Ironically, the DataContractJsonSerializer was able to convert JSON ‘objects’ back to an interface, say, ICustomer, while NewtonSoft serializer complains about  not being able to cast from an object to ICustomer. It might happen with your project as well, as long as (not sure however) there is no ambiguity on which class it should instantiate for ICustomer. (The KnownTypeAttribute, normally should fix this). The NewtonSoft Serializer, allows you to utilize an attribute. This code below, also might save you some hours research on how to fix that. (Sorry, this time it is C#, yeah)

Here we use the attribute:

[DataMember]
[JsonConverter(typeof(PersonConverter))]
public ICustomer Customer { get; set; }

And here you have the class. Just in case a concept you need it and how you use it!

public class PersonConverter : JsonCreationConverter
    {
        protected override Person Create(Type objectType, JObject jObject)
        {
            if (FieldExists("Initials", jObject))
            {
                return new Person();
            }
            if (FieldExists("Type", jObject))
            {
                return new Contact();
            }
            return null;
        }
        private static bool FieldExists(string fieldName, JObject jObject)
        {
            return jObject[fieldName] != null;
        }
    }
    public abstract class JsonCreationConverter : JsonConverter
    {
        ///


        /// Create an instance of objectType, based properties in the JSON object
        ///

        /// type of object expected
        ///
        /// contents of JSON object that will be deserialized
        ///
        ///
        protected abstract T Create(Type objectType, JObject jObject);

        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }

        public override object ReadJson(JsonReader reader,
                                        Type objectType,
                                         object existingValue,
                                         JsonSerializer serializer)
        {
            // Load JObject from stream
            var jObject = JObject.Load(reader);

            // Create target object based on JObject
            var target = Create(objectType, jObject);

            // Populate the object properties
            serializer.Populate(jObject.CreateReader(), target);

            return target;
        }

        public override void WriteJson(JsonWriter writer,
                                       object value,
                                       JsonSerializer serializer)
        {
            //default easy muke
            serializer.Serialize(writer, value);
        }
    }

Annother quirk that might bite you, is the fact that a WCF REST service, defaults to return application/xml content while Web API defaults to application/json. If a client application did not specify the Accept Header or even specifies ‘text/html’. This line below fixes the default output again to application/xml.

config.Formatters.XmlFormatter.Support

Howto use Paypal Instant Payment Notification (IPN) with character encoding0 Reacties

The C# sample at the IPN website, is a little bit outdated, since paypal currently supports international  character sets (great!) so if a chinese customer would enter his name in Chinese for instance :), it still would be kept ok. So to not corrupt the characters, we have to stick the same format as paypal sends us. This would be simple if the page would be a web service form, it is not, so we have to wrap the POST request and do the encoding all yourselves.

using System;

using System.Collections;

using System.Collections.Specialized;

using System.IO;

using System.Text;

using System.Net;

using System.ComponentModel;

using System.Data;

using System.Web;

 

using CSession;

namespace pp

{

      ///

      /// Summary description for ipn.

      ///

      public partial class ipn : System.Web.UI.Page

      {

            private bool isTesting;

            // this page runs as LCID 1033 english

            // and is tested on codepage 65001. you must –enable- the codepage

            // on your paypal preferences!

            // note that the shopping basket, is based on ISP Session from

            // http://www.nieropwebconsult.nl/asp_session_manager.htm

            // it integrates with classic asp as well as .NET

            protected void Page_Load(object sender, System.EventArgs e)

            {

                 

                  NameValueCollection rq = Request.Form;

                  //Response.BufferOutput = false;

                  string test_ipn = rq["test_ipn"];

                  isTesting = test_ipn != null && test_ipn == "1"? true: false;

                  // Create the request back

// instead of pp_url you could read https://www.paypal.com/cgi-bin/webscr

                  HttpWebRequest req = (HttpWebRequest) WebRequest.Create(

      System.Configuration.ConfigurationSettings.AppSettings["pp_url"]);

           

                  string version = rq["notify_version"]; // ' 2.1 currently

                  // Set values for the request back

                  req.Method = "POST";

                  string notifyCmd = "cmd=_notify-validate";

                  req.ContentType = "application/x-www-form-urlencoded";     

            // the charset will be utf-8 if set as preference on paypal!

            Encoding enc = System.Text.Encoding.GetEncoding(rq["charset"]);

           

            StreamWriter tw = new StreamWriter(new MemoryStream());

            int keyCount = rq.Count;

            // here goes our first command

            tw.Write(notifyCmd);

            //tricky part. If you do Request.Form.ToString() the & and the = is encoded as well!

            for (int xx = 0; xx < keyCount; xx++)

            {  

                tw.Write('&');

                tw.Write(HttpUtility.UrlEncodeToBytes(rq.GetKey(xx), enc));

                tw.Write('=');

                tw.Write(HttpUtility.UrlEncodeToBytes(rq.Get(xx), enc));

            }

           

            int contLen = (int)tw.BaseStream.Position;

            req.ContentLength = contLen;

            // Write the request back IPN strings in binary form

            // do not use a TextWriter since that would cause

            // encoding on encoding which obviously will fail

            byte[] temp = new byte[contLen];

            tw.BaseStream.Position = 0;

            tw.BaseStream.Read(temp, 0, contLen);

            Stream stOut = req.GetRequestStream();

            stOut.Write(temp, 0, contLen);

            stOut.Flush();           

 

            // Do the request to PayPal and get the response

            StreamReader stIn = new StreamReader(req.GetResponse().GetResponseStream(), enc);

                  string strResponse = stIn.ReadToEnd();

                  stIn.Close();

                  string Item_number, Payment_status, Txn_id, sessionId;

                  Item_number = rq["item_number"];

                  sessionId = rq["invoice"];

                  Txn_id = rq["txn_id"];

                 

                  db dbl = new db();

                  dbl.sqlConn.Open();

                 

                  DataSet1.tLicenseRow lic =  dbl.dataSet11.tLicense.NewtLicenseRow();

                  lic.CtryISO2Code = rq["address_country_code"];

                  lic.email = rq["payer_email"];

                  //TODO: lic should be fullfilled

                  lic.sessionID = new Guid(sessionId).ToByteArray();

                  lic.transactionID = Guid.NewGuid();

            lic.salesBlob = rq.ToString().Replace("&", "\r\n");

                  lic.saleDate = DateTime.Now;

                  lic.total_value = decimal.Parse(rq[