I want to implement a certain functionality, but I do not know where to start. I will describe what I have.
Backend
public enum SourceType { Database, Folder }
public class DatabaseSource
{
public string ServerName { get; set; }
public string DatabaseName { get; set; }
}
public class FolderSource
{
public string FolderName { get; set; }
}
public class TestController : ApiController
{
[HttpPost]
[Route("source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}
Frontend
export enum SourceType {
Database,
Folder
}
export class DatabaseSource {
public ServerName: string;
public DatabaseName: string;
}
export class FolderSource {
public FolderName: string;
}
var source = new DatabaseSource();
source.ServerName = "serverName";
source.DatabaseName = "dbName";
var obj = {
sourceType: SourceType.Database,
source: source
};
Now imagine that I will send obj to the server. I want that specific controller method to be called depending on the enum. How can I do this?
P.S. The example is greatly simplified.
Your implementation is inconsistent for what you've specified in code.
On the front-end you are describing an object which has a sourceType field and a source object property, while on the backend you're overloading the ApiController method and mapping different REST object resources to a single HTTP method and endpoint (which I believe will not work).
There is no magic way for the ApiController to use your enum property to differentiate between the object types automatically.
A simpler (and better) implementation would be to have separate ApiController classes for your Database and Folder source object POST calls. This follows the principle of REST API design where you are essentially mapping basic CRUD operations to the HTTP methods with object types.
If your intention is to perform an operation based on these parameter objects, then clarify the intention via the API routing for the endpoint as below:
public class TestController : ApiController
{
[HttpPost]
[Route("ETLLoad/Database/source")]
public void Post([FromBody]DatabaseSource source) //method one
{
}
[HttpPost]
[Route("ETLLoad/Folder/source")]
public void Post([FromBody]FolderSource source) //method two
{
}
}
Related
I want to make universal JSON generator for any ViewModel received from frontend. I found here that I can get type from string, but I do not know how to implement this in my case.
My idea was to send from Angular array with 2 values, first would be string that say what type is my ViewModel, and second value would be ViewModel, which I need to convert to JSON. (I need this JSONon backend for converting to other file formats, and I have some special requirements, like change of name property, etc.)
I am using MediatR, and here are my classes:
GenerateJSONQuery is input object, the one I will get from frontend.
public class GenerateJSONQuery<T> : IRequest<string>
{
public string TypeOfList { get; set; }
public List<T> Data { get; set; }
}
GenerateJSONQueryHandler is MediatR handler that will do reflection to ViewModel and generate JSON.
public class GenerateJSONQueryHandler<T> : IRequestHandler<GenerateJSONQuery<T>, string>
{
private readonly IddeeaODPDbContext _context;
private readonly IMapper _mapper;
public GenerateJSONQueryHandler(IddeeaODPDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public async Task<string> Handle(GenerateJSONQuery<T> request, CancellationToken cancellationToken)
{
// logic for generating files, in this part I need to somehow convert
// `request.Data` to specific List<T> where
// T can be e.g. `NewbornByBirthDateViewModel`,
//`IssuedDocumentsViewModel`, `RegisteredVehiclesViewModel`, etc. etc.
}
Controller that connect IRequest and IRequestHandler is:
public class GenerateFilesController : ApiBaseController
{
public GenerateFilesController(IOptions<AppSettings> appSettings) : base(appSettings)
{
}
[HttpPost]
[SwaggerOperation(Tags = new[] { "Administration/Document" })]
public async Task<string> List<T>([FromBody] GenerateJSONQuery<T> data, [FromHeader] string Authorization)
{
return await Mediator.Send(data);
}
}
and NewbornByBirthDateViewModel is example VieWModel that I need to serialize into JSON.
public class ClientNewbornByBirthDateViewModel
{
[TranslatedFieldName("Identifier", LanguageEnum.EN)]
public int Id { get; set; }
public string Institution { get; set; }
[TranslatedFieldName("Men", LanguageEnum.EN)]
public int MaleTotal { get; set; }
[TranslatedFieldName("Women", LanguageEnum.EN)]
public int FemaleTotal { get; set; }
public int Year { get; set; }
public int Month { get; set; }
}
I am pretty sure that my thinking way is bad, and that I need to do some kind of reflection, but I do not know how. I can not send only type of ViewModel from frontend, and then select all from db with context.Set<T>() because there can be filters, and those filters depends on which ViewModel is selected, so I must pass object with data from frontend to JSONGenerate logic and then reflect it to specific ViewModel on backend.
Your application must first understand classes and their types before attempting to use reflection by passing the data type name as a parameter.
For that get all the data types using reflection on which you want to
reflect your data on then filter out by using
TypeOfList.
Use this link to get all classes details within a namespace.
How can I get all classes within a namespace?
We are wrapping a Viewmodel inside a ApiRequestModel and pass it as a parameter to the DoAuditModel web Api method.DoAuditModel calls the DoAudit generic method.
Since there are multiple Viewmodels we had to create a ApiRequestModel for each Viewmodel type.
But we don't want to create a Api method/endpoint for each model.
We want to create a single Api method that can do this DoAuditModel task.
Below is our simplified model structure for API Method call.
It has some common data like Token AppCode and a Model (ViewModel).
These models (ModelA,ModelB etc) don't have a base class or implements an interface.
// Api Request Base
public class ApiRequest : IApiRequest
{
public string Token { get; set; }
public string AppCode { get; set; }
...
}
// Api Request For ModelA
public class ApiRequestForModelA : ApiRequest
{
public ModelA MyModel { get; set; }
...
}
// Api Request For ModelB
public class ApiRequestForModelB : ApiRequest
{
public ModelB MyModel { get; set; }
...
}
API Controller has multiple methods for each model type that calls a generic method.
public class MyAuditController : ApiController
{
[HttpPost]
public Task DoAuditModelA(ApiRequestForModelA modelReq)
{
// Generic Method
DoAudit<ApiRequestForModelA>(modelReq.MyModel);
...
}
[HttpPost]
public Task DoAuditModelB(ApiRequestForModelB modelReq)
{
// Generic Method
DoAudit<ApiRequestForModelB>(modelReq.MyModel);
...
}
}
I want to avoid this web method duplication (DoAuditModelA) and creating ApiRequest models (ApiRequestForModelA) for each model type since all I want is to call that generic DoAudit<T>(T model){...}
I want to create a single Api method as AuditModel.
So I created a generic Api Request as below.
// Generic API Request
public class ApiRequest<T> : ApiRequest
{
public ApiRequest(T model)
{
this.MyModel = model;
}
public T MyModel { get; set; }
...
}
Now the problem is that Web Api method doesn't know the model type and how to bind/deserialize data.
I ended up using ApiRequest<dynamic>.
Below is my new Api Method.
[HttpPost]
public Task DoAuditModel(ApiRequest<dynamic> modelReq)
{
var myModelObj = modelReq.GetType().GetProperty("MyModel").GetValue(modelReq);
// Get model Type
var typeData = auditReq.ModelAssemblyQualifiedName;
Type t = Type.GetType(typeData);
// how to use this t to create model instance or convert/deserialize data
var myModel = //myModelObj should cast to the original model type.(I'm stuck here)
DoAudit(myModel);
...
}
// DoAudit Generic method
private void DoAudit<T>(T myModel)
{
...
}
So this is my approach to pass these ViewModels to DoAudit generic method in the web api and avoid multiple web api endpoints. I'm also concern about what type of overhead that Api will have to handle is this approach. Generic controller will not work for me here.
Summery
I want to create a single API Endpint for DoAuditModel task which calls DoAudit generic method
I've a WCF service with one method which will be called from multiple web API controllers like in the below code.
public string Print(PdfPrinterRequest _request)
{
PdfPrinterService.PdfPrinterClient _Client = new PdfPrinterService.PdfPrinterClient();
PdfPrinterResponse _response = new PdfPrinterResponse();
return _Client.Print(_request.Document, out _pdfResponse);
}
PdfPrinterRequest(Document class) is the entity which I'm passing to get the response message from WCF service.Currently the document class holds few properties(REquest Header). I would like to call the same Print method from other API and pass type 'Customer' to WCF service. How can i achieve this? Can anyone please suggest me the correct implementation?
Below is my WCF service code,
public class PdfPrinterService : IPdfPrinter
{
public PdfPrinterResponse Print(PdfPrinterRequest request)
{
return PdfPrinterFacade.PrintPdf(request);
}
}
public static PdfPrinterResponse PrintPdf(PdfPrinterRequest request)
{
PdfPrinterResponse response = new PdfPrinterResponse();
//Process the request and send back the response message
}
[MessageContract]
public class PdfPrinterRequest
{
private Document _document;
[MessageBodyMember]
public Document Document
{
get { return _document; }
set { _document = value; }
}
}
How to pass a dynamic class object as a parameter in place of PdfPrinterRequest which is no bound to only one type(Document)? Please suggest.
Thanks,
If this service does not need to be interoperable, you can switch to NetDataContractSerializer, which uses full .NET type information, and is able to serialize many more types (but not any type - that's impossible).
Grab the UseNetDataContractSerializerAttribute from this answer and apply like so:
[UseNetDataContractSerializer]
public class PdfPrinterService : IPdfPrinter
{
public PdfPrinterResponse Print(PdfPrinterRequest request)
{
return PdfPrinterFacade.PrintPdf(request);
}
}
[MessageContract]
public class PdfPrinterRequest
{
[MessageBodyMember]
public object Document { get; set; }
}
I have the following classes:
class SomethingBase
{
public string SharedProperty { get; set; }
}
class ChildClassOne : SomethingBase
{
public string SpecificPropertyOne { get; set; }
}
class ChildClassTwo : SomethingBase
{
public string SpecificPropertyTwo { get; set; }
}
And I have ASP.NET MVC View which has two HTML-forms. These forms are calling the same action method.
This action method should receive any of two SomethingBase class derivatives.
However, if I create single parameter like SomethingBase param, then only the SharedProperty is received. This behavior can be explained by binding mechanism of ASP.NET MVC.
To make my action method work I created the next definition:
public ActionResult(ChildClassOne param1, ChildClassTwo param2)
SharedProperty goes to both params, but specific properties are populated only for object, which was actually passed from view. It works, but I don't think that this is the only solution.
Are there some best-practice solutions for this situation?
You should create a view model for each action since they are not alike. There's really no reason to try to use a base class in this case.
Method TryUpdateModel of Controller class make it work. However, this way is not pretty elegant.
...
public ActionResult Save(FormCollection collection)
{
SomethingBase model = null;
if (collection.AllKeys.Contains("SpecificOne"))
{
model = new ChildOne();
TryUpdateModel<ChildOne>((ChildOne)model, collection);
}
else
{
model = new ChildTwo();
TryUpdateModel<ChildTwo>((ChildTwo)model, collection);
}
...
Before there was "web api", one had to do actions of the type JsonResult GetPersons(..). Now, with web api, one can have List<Person> GetPersons(..).
I thought the whole point of this was to reutilize the actions, that is: call GetPersons from another action (maybe ActionResult GetPersons(..)).
But after many serialization problems I'm figuring out that this is not an option. For example, as simple as if the object has an enum inside, it can't be serializated to json.
So I ended up with many dynamic X(...) returning anonymous types and I cant really reuse many things of my API. Anny suggestions?
A example of a repeated code is the following:
Json:
from a in b select new { ... }
Not json
from a in b
Also, I've read in many forums that is not good to return the EF object itself, and thats exactly what web api motivates (and the existence of [ScriptIgnore])
The question: How do I reuse queries in the API and in the normal controllers?
How do I reuse queries in the API and in the normal controllers?
By not defining the queries in your API or MVC controllers. You can define the queries in a shared assembly, external to the MVC project, and have the controllers call into that layer.
Example:
Externalized
public interface IQuery<TResult> {}
public interface IQueryProcessor
{
TResult Execute<TResult>(IQuery<TResult> query)
}
public class MyQueryObject : IQuery<MyEntity[]>
{
public string QueryParam1 { get; set; }
public int QueryParam2 { get; set; }
}
API Controller
public class MyApiController : ApiController
{
private readonly IQueryProcessor _queryProcessor;
public MyApiController(IQueryProcessor queryProcessor)
{
_queryProcessor = queryProcessor
}
public IEnumerable<MyApiModel> Get
([FromUri] string queryParam1, int queryParam2)
{
var query = new MyQueryObject
{
QueryParam1 = queryParam1,
QueryParam2 = queryParam2,
};
var results = _queryProcessor.Execute(query);
return Mapper.Map<IEnumerable<MyApiModel>>(results);
}
}
MVC Controller
public class MyMvcController : Controller
{
private readonly IQueryProcessor _queryProcessor;
public MyMvcController(IQueryProcessor queryProcessor)
{
_queryProcessor = queryProcessor
}
public ViewResult Index(string queryParam1, int queryParam2)
{
var query = new MyQueryObject
{
QueryParam1 = queryParam1,
QueryParam2 = queryParam2,
};
var results = _queryProcessor.Execute(query);
var models = Mapper.Map<IEnumerable<MyViewModel>>(results);
return View(models);
}
}