Add constructors to auto-generated OpenAPI service client - c#

I've got a Blazor WebAssembly project with an ASP.NET WebAPI hosted service. If I use the auto-generated code in the "Connected Services" in Visual Studio to retrieve the OpenAPI definition, I get a nice proxy representing all of the HTTP endpoints, complete with request and reply objects.
But the generated request/reply classes have only a default constructor and properties, like:
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.0.22.0 (Newtonsoft.Json v11.0.0.0)")]
public partial class GetDetailedMessageRequest
{
[Newtonsoft.Json.JsonProperty("id", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public long Id { get; set; }
}
I prefer to have at least the option of a constructor with each of the properties - this is actually the way the classes are in the service side:
public class GetDetailedMessageRequest
{
public GetDetailedMessageRequest() { }
public GetDetailedMessageRequest(long id)
{
ID = id;
}
public long ID { get; set; }
}
Is there any way to either have the code generator build these constructors, or tell it not to create the model classes at all, and instead let both the client reference the shared models project?
Neither of these seems likely - I can think of a couple workarounds, like manually editing the generated code, or creating the request on the client using the class from my shared project, serializing it, deserializing it into the generated class type, or possibly just building the constructors again in new partial classes for all of these...But obviously those are not ideal.
I could always build my own code generator, or skip the generated stuff altogether and just use HttpClient normally - but I like the idea of an auto-generated proxy class.

Related

Why I get Unsuported Media Type on GET request? (.NET 6)

I'm surely missing something, because most questions around 415 error are referring to POST requests.
In this case, this is a very simple GET request, which works when I enumerate all action parameters, but if I create a simple DTO to contain all of these, I start receiving a 415 error.
I'm calling the api with https://localhost:555/some/test?field1=aaa&field2=1
This works:
[ApiController]
public class SomeController : ControllerBase
{
[Route("Test")]
public SomeResponse GetSomeResponse(string field1, int field2)
{
return new SomeResponse(field1, field2);
}
}
But this doesn't:
[ApiController]
public class SomeController : ControllerBase
{
[Route("Test")]
public SomeResponse GetSomeResponse(SomeRequest request)
{
return new SomeResponse(request.Field1, request.Field2);
}
}
public class SomeRequest
{
public string Field1 { get; set; }
public int Field2 { get; set; }
}
public class SomeResponse
{
public Someresponse(string field1, int field2)
{
Field1 = field1;
Field2 = field2;
}
public string Field1 { get; set; }
public int Field2 { get; set; }
}
The controller class is only using Microsoft.AspNetCore.Mvc;
When I try to use SomeRequest class, the API answers "415 - Unsuported Media Type"
The only difference is the way to receive the values, I'm not switching from uri to body (which could be json or plain text, etc.)
But since I'm not using the body, I can't understand which media-type it is referring to
My startup class is the same as the WeatherForecast, created with the project template ASP.NET Core Web API, with Visual Studio 2022, .Net6
Well, a possible solution is to specify [FromQuery]:
public SomeResponse GetSomeResponse([FromQuery] SomeRequest request)
Tho, I am not very happy with this as reaction to "Unsuported Format", so, other suggestions are welcome.
Ben Foster has a great writeup on this in Custom Model Binding in ASP.NET 6.0 Minimal APIs
I haven't tried it myself yet but adding this TryParse method to your SomeRequest type might help:
static bool TryParse(string? value, IFormatProvider? provider, out T parameter)
Generally I try not to bind objects to HttpGet because standard object serialization uses characters that have other meanings in standard URL routing and therefore need to be escaped in the url, which just over complicates the server and the client implementation.
I support the concept for common complex type style structures like Point that might be reused thought you application in many controllers and endpoints, but to create an object wrapper for each request would be ludicrous, that is a common client side technique in generated clients but we try to avoid that on the server-side.
I recommend against binding objects as GET parameters as it is a non-standard style of code that introduces a level of technical debt to the solution both in terms of API management and client implementation.
Even if you use custom binders or route handlers these solutions end up being a lot more custom code than it would have been to use primitive parameters and map them to your preferred type implementation in the first line of your endpoint method.
You should use the HttpGetAttribute to bind the request to query string parameters and specifically scope the requests so that only GET is allowed:
[HttpGet("Test")]
public SomeResponse GetSomeResponse2(SomeRequest request)
{
return new SomeResponse(request.Field1, request.Field2);
}
It is subtle but since .Net 5 (when FromUriAttribute was replaced and the various OData and MVC routing mechanisms were combined into a common pipeline) we are encouraged to use HttpGetAttribute (Or the other Http Method variants) instead of RouteAttribute as a way of minimising the configuration.
This code has similar functionality to the answer provided by #Zameb but it prevents users from attempting to use POST to access this endpoint. [FromQuery] creates a controller that even when a JSON object is POSTed to the endpoint the parameters are specifically mapped to the Query Parameters.

WCF Updating Service References doesnt add new propertys

i created a Backend-Service (Windows-Service) wich provide Data to my Network-Clients over WCF, handles the connection to the Database and some specific tasks.
Everytime when changed something in my DTO-Objects the changes were made correctly on the client side when i update the service-reference.
But now the Update-Process does not create the correct proxy for the WCF-Service.
When i add some DTO-Objects the information abount the new DTO's updated correctly to the client but when i add some Propertys to existing DTO-Objects the Update-Servicereference Function does not include the new Propertys.
I already tried to create a completely new application and add the Service-Reference within this Test-Scenario but also in this case the new property does not appear in the proxy-class.
First time i noticed this behaviour was as i try to create a new property in my "File.cs" DTO. I thinked that the name "File" (the class definition has the same name) creating this error. So i decided to rename the "File" DTO-Class to AttachmentFile and the new propertys are created correctly on the proxy.
But now i try to add Propertys to the Classes DeviceStayType and ProcessStateType and theres the same behaviour. No Error is displayed and the Git says that the proxy changed when i press Update Service-References but the propertys are still missing on the client side.
Here are some snippets:
The old DeviceStayType-Class:
[DataContract]
public class DeviceStayType : TypesBase
{
}
The new DeviceStayType-Class:
[DataContract]
public class DeviceStayType : TypesBase
{
[DataMember(Name = "TableName")]
[MaxLength(200)]
public string TableName { get; set; }
}
The generated Proxy for the DeviceStayType
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "4.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="DeviceStayType", Namespace="http://schemas.datacontract.org/2004/07/ProductLifecycle.Backend.Models.DTO")]
[System.SerializableAttribute()]
public partial class DeviceStayType : ProductLifecycle.Frontend.CommunicationService.TypesBase {
}
Hope that anyone can help :(
Thanks,
Michael
OK. After some tests i decided to outsource the Models in an DLL which both projects (backend and frontend) have a reference for.
It seems that this was the only way to fix this issue. I think this behaviour is produced when theree are two webservices; one in the frontend as Callback (Streamed because theres a much better performance) and one in the backend as Managing-Service. Both services used the same classes and i thought this could be a possible reason because the Backend-Service sends the Models to the Frontend-Client and the Frontend-Service sends the Models to the Backend-Client as well.
Now with the outsourced classes and much lighter conversions the scenario works in my case.

Sharing WSDL types without XSD

I can't seem to find an example of generating proxies from WSDLs with shared types but without having any XSDs to go along with them. Can anyone please mark this as duplicate and point me to an example please?
Here are 2 services, each has its own namespace and a common type. The only thing that is publicly accessible are their WSDLs, there is no type's XSD or its .dll to pass to wsdl.exe /sharedtypes or svcutils and without it I end up with identical class Foo that I can't pass in to SetFoo and class Foo1.
The best I could come up with is generating proxies programmatically and detecting duplicates via CodeDOM, ignoring DataContract/WebServiceBinding namespaces, but it's a huge mess...
[WebService(Namespace = "http://tempuri.org/FOO1")]
public class Service1 : WebService
{
[WebMethod]
public Foo GetFoo()
{
return new Foo();
}
}
[WebService(Namespace = "http://tempuri.org/FOO2")]
public class Service2 : WebService
{
[WebMethod]
public void SetFoo(Foo foo)
{
}
}
public class Foo
{
public int Bar { get; set; }
}
There is a way of doing this, which is outlined here.
In your case you can skip the first step, generate the proxy from service 1 and then use the /r flag on svcutil to reference the service 1 proxy assembly when you generate your service 2 proxy.
This will ensure your service 2 proxy will use the same instance of Foo from your service 1 proxy.
However, have you considered just hosting a single service with two operations? It would save you a lot of work.
Edit: Also have a look at this post:
http://blogs.msdn.com/b/youssefm/archive/2009/10/09/reusing-types-in-referenced-assemblies-with-svcutil-s-r-switch.aspx
First off, you need to set the [DataContract(Namespace="some namespace here")] for all common service data types, otherwise when the WSDL and XSDs are generated then you will have objects from two difference namespace --- this is absolutely essential. The namespace value will only apply to the types defined in the XSD and not in the WSDL. XSD = data, WSDL = service.
The XSDs and WSDL and generated if, and only if, you have the META service behavior set - add this behavior and then you can navigate to the URL. The URL of the META service behavior will then have a link to your WSDLs and XSDs.
I use the following piece of code to self-host services in windows services rather than through IIS, however the same principals apply....
/// <summary>
/// Enables meta data output for a service host.
/// </summary>
/// <param name="host">The service host.</param>
/// <remarks>Must be invoked prior to starting the service host.</remarks>
public static void SetupMetaDataBehaviour(ServiceHost host)
{
ServiceMetadataBehavior metaDataBehaviour = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
if (metaDataBehaviour == null)
{
metaDataBehaviour = new ServiceMetadataBehavior();
metaDataBehaviour.HttpGetEnabled = true;
host.Description.Behaviors.Add(metaDataBehaviour);
}
else
{
metaDataBehaviour.HttpGetEnabled = true;
}
}
after adding your two web references:
double click on the second web service reference
in the object browser navigate to the definition of Foo
right click on Foo and choose go to definition
delete the definition for the class Foo
add a using statement for the namespace of webservice one
find and replace all instances of <namespace-of-service-reference-2>.Foo with just Foo
This should fix your problem as it forces the autogenerated code for both service references to use the same class declaration.

Get custom object via WCF in an Explorer-Extension isn't working but in Winform

I have a WCF Host with something like this:
[ServiceContract]
public interface IMountToOs
{
[OperationContract]
char GetMountDriveLetter();
[OperationContract]
MyTestClass MyTest();
}
public class MyTestClass
{
public string A { get; set; }
public string B { get; set; }
}
Client
private IMountToOs _proxy;
public IMountToOs Proxy
{
get
{
if (_proxy == null)
{
NetTcpBinding binding = new NetTcpBinding();
binding.MaxReceivedMessageSize = 2147483647;
binding.OpenTimeout = TimeSpan.FromMilliseconds(50000);
EndpointAddress address = new EndpointAddress("net.tcp://localhost:1234/MountToOsHost");
//_proxy = new MountToOsClient(binding, address);
ChannelFactory<IMountToOs> factory = new ChannelFactory<IMountToOs>(binding);
_proxy = factory.CreateChannel(address);
}
return _proxy;
}
}
While I can access
MessageBox.Show("Okay - " + Proxy.GetMountDriveLetter());
I can't call this method:
MessageBox.Show("Okay - " + Proxy.MyTest().A);
The complete extension is not working. But only while using it in an extension. Even if I insert a Messagebox in the first line of the extension it is not hit. I don't know why. It seems to run a pre-check and find the call of the custom class which is refused or so...
If I use a winform or so there is no problem.
.net 3.5
curious is that I have a break-point and a message of the hosts side. So I see that the method is not called
Update
now I moved the wcf-call in the Load Method of the extension and get a exception:
System.MissingMethodException: method not found:
"Contracts.Interfaces.MyTestClass
Contracts.Interfaces.IMountToOs.MyTest()".
My winform test and this extension use the same interface so that the method should known from both. no contract or so is outdated
According to what I found here and in the comments of the post: "For creating dynamic service proxy using client channel factory method, you will need datacontracts of the service. If you don't have datacontracts but you have the service URL, then you could use reflection to create proxy at runtime and call the service method."
Seems that the MyTestClass type is not known on the client side, so I think you could use reflection, or share the class between the client and server or much more simple, use the datacontract attribute.
Also, found something on MSDN that says something like this:
"When to use a proxy?
We create proxy using svcutil.exe. The output of this tool gives a proxy class and makes corresponding changes to the application configuration file. If you have a service that you know is going to be used by several applications or is generic enough to be used in several places, you'll want to continue using the generated proxy classes. We use proxy in WCF to be able to share the service contract and entities with the client. Proxies have several restrictions like they need to have gets and sets , contructors can't be exposed , methods other than the service contract cannot be exposed, repetition of code, everytime that we add/modify a service contract/data contract/message contract we need to re-generate the proxy for the client.
When to use ChannelFactory
The other option is using the ChannelFactory class to construct a channel between the client and the service without the need of a proxy . In some cases, you may have a service that is tightly bound to the client application. In such a case, it makes sense to reference the Interface DLL directly and use ChannelFactory to call your methods using that. One significant advantage of the ChannelFactory route is that it gives you access to methods that wouldn't otherwise be available if you used svcutil.exe..
When to use a ChannelFactory vs Proxy class?
A DLL is helpful if the client code is under you control and you'd like to share more than just the service contract with the client -- such as some utility methods associated with entities and make the client & the service code more tightly bound. If you know that your entities will not change much and the client code is less, then a DLL would work better than a proxy. If the client to your service is external to the system, such as API, it makes sense to use a proxy, because it makes sharing the contract easier by giving a code file rather than a DLL."
We cant see the class
MountToOsClient: IMountToOs
So we can only assume it is ok.
[DataContract] // Missing
public class MyTestClass
{
[DataMember] // Missing
public string A { get; set; }
[DataMember] // Missing
public string B { get; set; }
}
MountToOsClient can not expose Mytestclass without these attributes.

Class constructor (from C# web service) won't auto-implement properties in C# MVC

I'm running two instances of VS2010 on my local machine. One instance is running my Web Service (written in C#). The other instance is running my MVC web app (also C#). The MVC web app has a reference to the web service. I can successfully invoke web service methods from within the MVC app.
In my web service is a PageNavigation class:
// PageNavigation.cs
using System;
using System.Collections.Generic;
using System.Text;
public class PageNavigation
{
public string Page_Number { get; set; }
public string Page_Count { get; set; }
public PageNavigation()
{
Page_Number = "1";
Page_Count = "2";
}
}
By default, this should return an object with auto-implemented properties when I call the class constructor:
WebService.PageNavigation pageNavigation = new WebService.PageNavigation();
This works when constructing a PageNavigation object elsewhere in the web service.
pageNavigation.Page_Number
"1"
pageNavigation.Page_Count
"2"
However, running the same line of code on the MVC isn't giving the same result; the object's properties are all null values.
pageNavigation.Page_Number
null
pageNavigation.Page_Count
null
Is this the expected behavior? Is there a way to populate the properties with default values as intended? If more information is needed please let me know and I will update the question.
The service reference only sees the schema of your object, not business logic; in your case, your service reference just created a shell data type in the MVC application. When you create a service reference, it's actually creating another type with the same property names and types as the type defined in the service.
For your particular scenario (simply providing default property values and not more general business logic), you should be able to apply the [System.ComponentModel.DefaultValue] attribute to your properties in order for the class generator to recognize that these properties should be populated with a default value.
Incidentally, if the service reference were reusing existing types (if you had this type in a common library that was referenced both by the service and the application, for example), then your business logic would be intact.
An alternative would be to implement a factory pattern, whereby you call a function on the web service that instantiates (and optionally populates) the data object, then returns it to the client.
Yes, this is expected behaviour. The MVC site is not actually using your PageNavigation class. It is a simple copy (generated when you add the web service reference) containing of all the properties, but none of the methods, including the constructor.
You could work around this by refactoring your service so the entities are in a separate assembly and then you can reuse this assembly on the client as an option when you generate the proxy.
If you insist on using the same types between client and service, then on the "Advanced" tab of the "Add Service Reference" dialog, you can choose to reuse the types in your server assembly.
I would move that class out of the service and into a class library project referenced by the service and by the client.
And I wouldn't do this for such a small reason as default values. this violates SOA by coupling the service and the client. It will obviously not work for clients which are not running .NET.
What serializer are you using to deserialize the response from the server? Some of them (like the DataContractSerializer for example) do not call the default constructor.
The solution that you should use if you are in fact using DataContractSerializer is to use the OnDeserialized attribute like this:
using System.Runtime.Serialization;
public class PageNavigation
{
public string Page_Number { get; set; }
public string Page_Count { get; set; }
public PageNavigation()
{
Init();
}
[OnDeserialize]
void Init()
{
Page_Number = "1";
Page_Count = "2";
}
}

Categories