I've got an asp.net core 2.2 web application using Razor Pages and Mediatr.
My query has private setters as described in Jimmy Bogard's blog:
public class Query : IRequest<Result>
{
public Query(string needle)
{
this.Needle = needle;
}
public string Needle { get; private set; }
}
And when i use it in my web api it's OK (even ConstructionHandling is noneed)
But when i use Razor Pages i'got an error 'cause there's no parameterless ctor in query:
public class SearchModel : PageBaseModel
{
public SearchModel(IMediator mediator)
: base(mediator)
{
}
[BindProperty(SupportsGet = true)]
public Accounts.Search.Query Query { get; set; }
public Accounts.Search.Result Result { get; private set; }
public async Task<IActionResult> OnGetAsync(CancellationToken cancellationToken)
{
Result = await this.Mediator.Send(Query, cancellationToken);
return this.Page();
}
}
Is it possible to use private setters for model binding (without writting custom IModelBinder for every query)?
Is it possible to use private setters for model binding (without writting custom IModelBinder for every query)?
emphasis mine
Short answer: NO
Long answer here Model Binding in ASP.NET Core: Complex types
A complex type must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor.
again emphasis mine
I believe how ever that you are mixing concerns by trying to use an immutable message request as a model for binding.
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?
Disclaimer: this is a long one. Unreasonable knowledge of ASP.NET Core MVC internals is almost certainly required. Here be dragons.
Background
I am trying to implement a method for augmenting type metadata in ASP.NET MVC Core.
The reason I want to do this is that my data models are used by multiple projects, so I've shared them by placing them in a NuGet package:
// defined in NuGet package
public class MyModel
{
public int MyProperty { get; set; }
public MyNestedModel NestedModel { get; set; }
}
public class MyNestedModel
{
public bool NestedProperty { get; set; }
}
^ Listing 1
However, some of the projects will need to apply additional metadata to the model types - for example, in the case of an ASP.NET Core project, these models will be used as inputs and therefore participate in model-binding, so they require FromQuery, FromHeader etc. attributes applied. I obviously cannot do this in the package, as different consumer projects will need to apply different attributes depending on their use cases.
The simplest and mostly guaranteed-to-work way is to modify all model properties to be virtual and then have subclasses that override those properties as necessary to add attributes:
// defined in NuGet package
public class MyModel
{
public virtual int MyProperty { get; set; }
public virtual MyNestedModel NestedModel { get; set; }
}
public class MyNestedModel
{
public virtual bool NestedProperty { get; set; }
}
// defined in ASP.NET MVC project consuming above package
public class MyViewModel : MyModel
{
[FromQuery(Name = "foo")]
public override int MyProperty { get; set; }
public new // can't use override, as type is different
MyNestedViewModel NestedModel { get; set; }
}
[Bind(Prefix = "")]
public class MyNestedViewModel : MyNestedModel
{
[FromQuery(Name = "bar")]
public override bool NestedProperty { get; set; }
}
^ Listing 2
I don't want to do this, because in every model that has a child model property, that child model has to be subclassed, and then it cannot be used as an override but has to be re-declared with new - and the semantics of new won't work for me here. Also, I don't really intend for the model types to be subclassed.
I'm aware of ModelMetadataTypeAttribute but the intended usage of that attribute is on the model type to be augmented, not the type doing the augmenting. Since in my case there's no way to know ahead of time what the actual metadata types will be (as they're defined in the consuming projects), I cannot use ModelMetadataTypeAttribute. Subclasses and partial classes will also not work - ModelMetadataTypeAttribute only applies to the class specified, partial cannot span across assemblies.
Solution
Essentially, a "reverse" version of ModelMetadataTypeAttribute that is applied on the type doing the augmenting, pointing back to the model type to be augmented:
// defined in ASP.NET MVC project consuming package from Listing 1
[ReverseModelMetadataType(typeof(MyModel))]
public class MyModelMetadata
{
[FromQuery(Name = "foo")]
public int MyProperty { get; set; }
public MyNestedModel NestedModel { get; set; }
}
[ReverseModelMetadataType(typeof(NestedModel))]
[Bind(Prefix = "")]
public class NestedModelMetadata
{
[FromQuery(Name = "bar")]
public bool NestedProperty { get; set; }
}
^ Listing 3
The intention is that at runtime, the model-binding infrastructure will pick up that MyModel.MyProperty should be bound using [FromQuery(Name = "foo")], i.e. from the query string as a variable named foo. Similarly, MyModel.NestedModel.NestedProperty should be simply bound as bar from the query string.
In order to make this work I've implemented a custom ModelMetadataProvider that inherits DefaultModelMetadataProvider and overrides the CreatePropertyDetails method:
// defined in same ASP.NET Core MVC project as Listing 3
public class MyModelMetadataProvider : DefaultModelMetadataProvider
{
protected override DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key)
{
var defaultPropertyDetails = base.CreatePropertyDetails(key);
// check if the key.ModelType should be augmented
// if so, mutate the relevant element(s) of the defaultPropertyDetails array to do the augmentation
return defaultPropertyDetails;
}
}
The mutation part effectively changes the relevant DefaultMetadataDetails.ModelAttributes member to have the additional attributes applied to the properties of the type(s) decorated with ReverseModelMetadataTypeAttribute. I then get ASP.NET MVC Core to use my provider instead of its DefaultModelMetadataProvider via the following in Startup.cs#ConfigureServices:
services.AddSingleton<IModelMetadataProvider, MyModelMetadataProvider>();
I have verified that provider is registered and the mutation code works correctly: MyModelMetadataProvider.CreatePropertyDetails is hit and the defaultPropertyDetails returned do contain the extra attributes applied by my *ModelMetadata classes from Listing 3.
Problem
Model binding does not work: ASP.NET Core MVC essentially behaves as if the attributes added via Listing 3 do not exist, and I don't know why. As far as I'm aware, the results of the calls to the various IModelMetadataProvider methods are cached then used for all subsequent lookup of metadata that's required, including for the purposes of binding models.
I'm hoping that somebody can provide some advice on how to get this to work, before I have to go through the pain of stepping through the ASP.NET Core source myself.
I have a Blazor Project, in the Program.cs(formaly aka Startup.cs) I added a service
builder.Services.AddSingleton<Models.UserVisit>();
I can use/access that service on a razor/blazor page like this :
#inject Models.UserVisit userVisitObj
(UserVisit is a "normal" C# .cs Class in the folder Models)
What I don't know is how can I use this "userVisitObj" in a normal C# Class that does not have a razorpage (where I would use the #inject)?
How do I use that in here (normal C# Class in the same project but without a blazor/razor-componentpage):
public class UserModel
{
[BsonId]
public Guid Id { get; set; }
public CultureInfo UserCultureInfo { get; set; }
...
public UserModel()
{
[Inject]
Models.UserVisit userVisitObj; // THAT DOESN'T WORK -- ERROR
}
}
I hope I could make my question somewhat clear (please be kind I'm still a beginner).
You can use constructor injection as follows:
private readonly Models.UserVisit _userVisitObj
public UserModel(Models.UserVisit userVisitObj)
{
_userVisitObj = userVisitObj;
}
Note that this is applicable to normal C# classes.
If your class is a component class, you need to use the Inject attribute with
a public property, as for instance:
[Inject]
public Models.UserVisit UserVisit {get; set;}
Hope this helps...
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);
}
}