WCF RESTful Console-hosted, return 400 Bad Request - wcf

Studying WCF RESTful, host in a Console,
my steps:
create sample models
create contract of service
create service
host this service in a console.
run this host, looks ok.
create a winform, use service address via json post to service host.
i hope it would work, but return http 400.
I tried on WCF(not REST) Console-hosted, WebAPI, steps all ok.
finally, stackoverflow.com
please help
Models
[Serializable]
public abstract class Building
{
public Manufacturer Manufacturer { get; set; }
}
[Serializable]
public class Manufacturer
{
public string Name { get; set; }
public string Telephone { get; set; }
}
[Serializable]
public class Furniture : Building
{
public string Name { get; set; }
}
[Serializable]
public class Reception
{
public int Floor { get; set; }
public int Number { get; set; }
}
[Serializable]
public class Room : Building
{
public string Number { get; set; }
public List<Furniture> Furnitures { get; set; }
}
[Serializable]
public class Hotel : Building
{
public Guid Guid { get; set; }
public List<Reception> Receptions { get; set; }
public List<Room> Rooms { get; set; }
public string Name { get; set; }
public string Address { get; set; }
}
Contract
[ServiceContract]
public interface IHotel
{
// Create objct Hotel
[OperationContract]
[WebInvoke(UriTemplate = "", Method = "POST", RequestFormat= WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json)]
bool Create(Hotel hotel);
}
Service
public class HotelService : I Hotel
{
public bool Build(Models.Hotel hotel)
{
if (hotel == null)
return false;
// codes here is object hotel(EF) creation, test OK
return true;
}
}
Host(Console)
WebServiceHost serviceHost = new WebServiceHost(typeof(Demo.Services.HotelService), new Uri("http://192.168.1.101/HotelService"));
ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(typeof(Demo.Contracts.IHotel), new WebHttpBinding(), "");
ServiceDebugBehavior sdb = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
sdb.HttpHelpPageEnabled = false;
Console.WriteLine("Starting Service...");
// start service
serviceHost.Open();
Console.WriteLine("Started, press RETURN to exit.");
Console.ReadLine();
serviceHost.Close();
Client(Winform)
Caller
public bool BuildHotel(string json)
{
WebRequest request = HttpWebRequest.Create("http://192.168.1.101/HotelService");
request.ContentType = "application/json";
byte[] data = Encoding.UTF8.GetBytes(json);
request.ContentLength = data.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(data, 0, data.Length);
requestStream.Close();
WebResponse response = request.GetResponse(); // 400 throwed here
Stream responseStream = response.GetResponseStream();
StreamReader responseStreamReader = new StreamReader(responseStream);
string result = responseStreamReader.ReadToEnd();
return true;
}
Json String for 'bool BuildHotel(string)' upon
{
"Guid":"ea59c011-d656-4870-b29b-30a44e668560",
"Receptions":[
{"Floor":1,"Number":1},
{"Floor":2,"Number":2}
],
"Rooms":[
{
"Number":"c",
"Furnitures":[
{"Name":"1","Manufacturer":{"Name":"1","Telephone":"1"}},
{"Name":"2","Manufacturer":{"Name":"2","Telephone":"2"}}
],
"Manufacturer":{"Name":"c","Telephone":"c"}
}
],
"Name":"x",
"Address":"x",
"Manufacturer":{"Name":"x","Telephone":"x"}
}

it expects such kind of JSON:
{"k_BackingField":{"k_BackingField":"4","k_BackingField":"4"},"k_BackingField":"x","k_BackingField":"ea59c011-d656-4870-b29b-30a44e668560","k_BackingField":"x","k_BackingField":[{"k_BackingField":1,"k_BackingField":1},{"k_BackingField":2,"k_BackingField":2}],"k_BackingField":[{"k_BackingField":{"k_BackingField":"3","k_BackingField":"3"},"k_BackingField":[{"k_BackingField":{"k_BackingField":"1","k_BackingField":"1"},"k_BackingField":"1"},{"k_BackingField":{"k_BackingField":"2","k_BackingField":"2"},"k_BackingField":"2"}],"k__BackingField":null}]}
To change it you can mark all your data contracts with DataContract and DataMember attribute:
[DataContract]
public abstract class Building
{
[DataMember]
public Manufacturer Manufacturer { get; set; }
}
In this case it will understand json as you have given us in the question and will process it successfully.

Related

How to use AutoMapper to map OData enum string in json request dto to entity enum property

I am working on a new ASP.NET Core 3.1.1 API with Microsoft.AspNetCore.OData v 7.3.0, AutoMapper v9.0.0 and Microsoft.AspNetCore.Mvc.NewtonsoftJson v3.1.1
I am getting the following error when I make a POST to the Accounts endpoint using Postman v7.18.0;
AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.
I have reviewed the similar questions list when creating this question but was unable to find a solution.
In reviewing google searches for AutoMapper OData Enums all I could find were the recommendation to decorate my dto class with...
[AutoMap(typeof(Account))]
... and to decorate my dto enum properties with ...
[JsonConverter(typeof(StringEnumConverter))]
However, I still get the error. I found references to using an AutoMapperProfile class with a mapper defined as
CreateMap<Account, AccountModel>().ReverseMap();
But it appears that AutoMapper v9.0.0 no longer has a CreateMap method. My understanding was that adding the [AutoMap(typeof(Account))] to the dto class had the same effect as creating the map in the profile class.
I feel like I am going in circles at this point here so I though I would reach out to the SO community. I am sure it is something simple, I am just not seeing it.
Here is my POST request body from Postman;
{
"#odata.context": "https://localhost:44367/v1/$metadata#Accounts",
"AccountName": "Test Provider",
"AccountType": "Provider",
"IsTaxExempt": false,
"Status": "Active"
}
Here is my AccountsController Post method;
[ODataRoute]
[Produces("application/json;odata.metadata=minimal")]
[ProducesResponseType(typeof(AccountModel), Status201Created)]
[ProducesResponseType(Status400BadRequest)]
public async Task<IActionResult> Post([FromBody] AccountModel record)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
record.Id = new Guid();
var entity = _mapper.Map<Account>(record);
_context.Add(entity);
await _context.SaveChangesAsync();
var createdRecord = _mapper.Map<AccountModel>(entity);
return Created(createdRecord);
}
Here is my Account entity class;
public class Account : EntityBase
{
[Required]
[Column(TypeName = "nvarchar(50)")]
[MaxLength(50)]
public string AccountName { get; set; }
public AccountTypes AccountType { get; set; }
public bool IsTaxExempt { get; set; }
}
Here is the EntityBase class;
public class EntityBase
{
[Required]
public Guid Id { get; set; }
public DateTimeOffset? DateTimeCreated { get; set; } = DateTime.UtcNow;
public DateTimeOffset? DateTimeLastModified { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public StatusTypes Status { get; set; }
public bool DeleteFlag { get; set; }
}
Here is my Account DTO class;
[Filter, Count, Expand, OrderBy, Page, Select]
[AutoMap(typeof(Account))]
public class AccountModel : BaseModel
{
[Required]
[MaxLength(50)]
public string AccountName { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public AccountTypes AccountType { get; set; }
public bool IsTaxExempt { get; set; }
}
Here is my BaseModel class;
[Select, Filter]
public class BaseModel
{
public Guid Id { get; set; }
public DateTimeOffset DateTimeCreated { get; set; } = DateTime.UtcNow;
public DateTimeOffset DateTimeLastModified { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public StatusTypes Status { get; set; }
public bool DeleteFlag { get; set; }
}
And here are my Enums for AccountTypes and StatusTypes
public enum AccountTypes
{
Customer = 0,
Reseller = 1,
Provider = 2,
}
public enum StatusTypes
{
Active = 0,
Inactive = 1,
}
Any ideas?
It turns out that I needed to create an instance of an AutoMapper MapperConfiguration and assign it to the mapper.
I ended up putting in in the constructor of the Controller, for example;
public AccountsController(CdContext context, IMapper mapper)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
var config = new MapperConfiguration(cfg => cfg.CreateMap<Account, AccountModel>().ReverseMap());
_mapper = new Mapper(config);
}
After I did this, everything worked as expected.
Here is a link to AutoMappers docs on the subject.

How to return a list of a defined type WCF service

I have defined the data contract as follows:
[DataContract]
public class TestResult
{
[DataMember]
public string[] NegResponses { get; set; }
[DataMember]
public bool Pass { get; set; }
[DataMember]
public string Request { get; set; }
[DataMember]
public string Response { get; set; }
}
Is it possible to return a list of the above type in the operation contract as follows:
[OperationContract]
[FaultContract(typeof(TestFault))]
List<TestResult> Tester(string nodeCaption);
And what else I have to look into to return a list of a type that has been defined ?
By the way I guess I am not using svcutil and instead using channel factory as follows:
private static readonly ITestService TestClient;
// initialize a channel factory
var channelFactory = new ChannelFactory<ITestService>(new NetTcpBinding(SecurityMode.None), endPoint);
// Create a channel
TestClient = channelFactory.CreateChannel();
The client-side and the server-side should share the same service contract and data contract. This is fundamental that we could consume the service on the client-side.
Here is an example, wish it is useful to you.
Server-side.
class Program
{
static void Main(string[] args)
{
Uri uri = new Uri("http://localhost:21011");
BasicHttpBinding binding = new BasicHttpBinding();
using (ServiceHost sh = new ServiceHost(typeof(MyService), uri))
{
sh.AddServiceEndpoint(typeof(IService), binding, "");
ServiceMetadataBehavior smb;
smb = sh.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (smb == null)
{
smb = new ServiceMetadataBehavior()
{
HttpGetEnabled=true
};
sh.Description.Behaviors.Add(smb);
}
Binding mexbinding = MetadataExchangeBindings.CreateMexHttpBinding();
sh.AddServiceEndpoint(typeof(IMetadataExchange), mexbinding, "mex");
sh.Opened += delegate
{
Console.WriteLine("Service is ready");
};
sh.Closed += delegate
{
Console.WriteLine("Service is clsoed");
};
sh.Open();
Console.ReadLine();
//pause
sh.Close();
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
List<Product> SayHello();
}
public class MyService : IService
{
public List<Product> SayHello()
{
return new List<Product>()
{
new Product()
{
ID=1,
Name="Apple"
},
new Product()
{
ID=2,
Name="Pear"
}
};
}
}
[DataContract(Namespace = "MyNamespace")]
public class Product
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
}
Client-side (Console application,we call the service with ChannelFactory).
class Program
{
static void Main(string[] args)
{
BasicHttpBinding binding = new BasicHttpBinding();
Uri uri = new Uri("http://10.157.13.69:21011");
ChannelFactory<IService> factory = new ChannelFactory<IService>(binding, new EndpointAddress(uri));
IService service = factory.CreateChannel();
try
{
var result = service.SayHello();
foreach (var item in result)
{
Console.WriteLine($"ID:{item.ID}\n,Name:{item.Name}");
}
}
catch (Exception)
{
throw;
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
List<Product> SayHello();
}
[DataContract(Namespace = "MyNamespace")]
public class Product
{
[DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
}
Data Contract should be decorated with Namespace property, this will guarantee that the serialization and deserialization process properly). Actually, the service contract also needs a namespace to represent the practical method name, which will be used to addressing the method on the server-side. but there is a default value in the namespace.
http://tempuri.org
Feel free to let me know if there is anything I can help with.

Not able to pass List<FilterData> using WCF with wsHttpBinging

I am not able to pass List using WCF with wsHttpBinging. List is a property of FilterResponse class.
Getting the following error.
-Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
//Following is the code.
[DataContract(Namespace = "Abc.Wao.Entity.Response")]
[CollectionDataContract]`
public class FilterResponse : Alcoa.Wao.Entity.Response.Response
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists"), DataMember]
public List<FilterData> FilterData { get; set; }
}
[KnownType(typeof(FilterResponse))]
[CollectionDataContract]
[DataContract(Namespace = "Abc.Wao.Entity.Response")]
public class Response
{
public Response()
{ }
[DataMember]
public string AuthToken { get; set; }
[DataMember]
public string Fault { get; set; }
[DataMember]
public Exception Exception { get; set; }
[DataMember]
public string SessionContext { get; set; }
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class WaoService : IWaoService
{
public FilterResponse GetFilterDetails()
{
FilterResponse res = null;
//Call factory
res = Abc.Wao.Factory.CommonFactory.GetFilterDetails();
return res;
}
}
//------------------------------------------------------
[ServiceContract]
[ServiceKnownType(typeof(FilterResponse))]
[ServiceKnownType(typeof(Response))]
public interface IWaoService
{
[OperationContract]
FilterResponse GetFilterDetails();
}
Your property in the DataContract is missing a DataMember attribute:
[DataMember]
public List<FilterData> FilterData { get; set; }

ServiceStack - calling 3rd party web service with class

I am trying to call a 3rd party web service
Their REST API uses the following URL style.
http://www.VoiceBase.com/services?version=1.0&apikey=your-apikey&password=secret&action=list&status=processing
All of their service calls go to the same /services
How do I create a class so the following would work?
var client = new JsonServiceClient("http://www.voicebase.com");
var response = client.Get<ResponseVoiceBaseListClass>(new VoiceBaseListClass());
Additional classes I have created but I am not quite there yet
public class VoiceBaseBaseClass
{
public string version { get; set; }
public string apikey { get; set; }
public string password { get; set; }
public VoiceBaseBaseClass()
{
this.version = "1.0";
this.apikey = "API";
this.password = "password";
}
}
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
public string action { get; set; }
public string status { get; set; }
public VoiceBaseListClass()
: base()
{
this.action = "list";
this.status = "processing";
}
}
public class ResponseVoiceBaseListClass
{
public string requestStatus { get; set; }
public string statusMessage { get; set; }
public string fileStatus { get; set; }
public List<string> mediaIds { get; set; }
public ResponseVoiceBaseListClass()
{
this.mediaIds = new List<string>();
}
}
Using the above classes the call that goes to the server is
/json/syncreply/VoiceBaseListClass?action=list&status=processing&version=1.0&apikey=API&Password=password
Is there a way I can force the service stack client to go to the
/Services
instead of
/json/syncreply/VoiceBaseListClass
I found a way to do this and it is working great for me.
[RestService("/services", "GET")]
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
}
Although this is a deprecated attribute - the new attribute is called Route
https://github.com/ServiceStack/ServiceStack/wiki/Release-Notes
Chris

Deserialization Error while returning the Object from WCF Service

I'm getting below shown error while returning the Activity object array.Not able understand where things are going wrong.Can any one help me with this .
Here is the error
End element 'ActivityTypeId' from namespace
'http://schemas.datacontract.org/2004/07/BusinessEntities' expected.
Found element 'a:Code' from namespace
'http://schemas.datacontract.org/2004/07/BusinessEntities'. Line 1,
position 450.
UI Related code:
protected void Page_Load(object sender, EventArgs e)
{
TimeSheetManagementServiceClient serviceClient = new TimeSheetManagementServiceClient("WSHttpBinding_ITimeSheetManagementService");
Activity[] activities=serviceClient.GetActivities();
GridView1.DataSource = activities;
GridView1.DataBind();
}
WCFService code
public class TimeSheetManagementService:ITimeSheetManagementService
{
public BusinessEntities.Activity[] GetActivities()
{
TimeSheetManagementDataController controller= new TimeSheetManagementDataController();
var activities = controller.GetActivities().Select(activity => new BusinessEntities.Activity()
{
Code = activity.Code,
Description = activity.Description,
Status =
(EntityStatus)
Enum.Parse(typeof(EntityStatus), ((activity.Status==true) ? 0 : 1).ToString()),
ActivityTypeId = new BusinessEntities.ActivityType()
{
Code=activity.ActivityType.Code,
Description = activity.ActivityType.Description,
Name = activity.ActivityType.Name
}
});
return activities.ToArray();
}
}
Service Contract
[ServiceContract]
interface ITimeSheetManagementService
{
[OperationContract]
Activity[] GetActivities();
}
Data Contract
[DataContract]
public class Activity
{
[DataMember]
public string Code { get; set; }
[DataMember]
public string Description { get; set; }
[DataMember]
public EntityStatus Status { get; set; }
[DataMember]
public ActivityType ActivityTypeId { get; set; }
}
[DataContract]
public enum EntityStatus
{
[EnumMember]
Active=0,
[EnumMember]
Inactive=1
}
[DataContract]
public class ActivityType
{
[DataMember]
public string Code { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Description { get; set; }
}
i`m not sure but i think the issue is the alphabetical order of the datamember of your Activity class. just for testing, consider specifying the order property in the Datamember attribute.
http://msdn.microsoft.com/en-us/library/ms729813%28v=vs.90%29.aspx
hope this will help