Set a ASP.NET WEB.API model property to be read-only for consumers of the API? - asp.net-web-api

What's the best way of ensuring that a property of a model can only be set by the ASP.NET WEB.API service? To a consumer of the service, that property is read-only.
For example:
public class MyModel
{
[Required]
public string CanBeSetByConsumer { get; set; }
// Can only be set by the service
public int Id { get; set; }
}
public class MyModelController : ApiController
{
public MyModel Get(int id)
{
// get MyModel by Id
return new MyModel();
}
public MyModel Post(MyModel myData)
{
// save myData to a store and generate an ID
// return myData with ID populated with a 201 Created
}
}
In the above example, the consumer of the API can POST:
{
"CanBeSetByConsumer" : "SomeValue"
}
The consumer can also GET:
{
"Id" : 1234,
"CanBeSetByConsumer" : "SomeValue"
}
What I would like to do is return a 400 BAD REQUEST if the client POSTs:
{
"Id" : 1234,
"CanBeSetByConsumer" : "SomeValue"
}

Here is one way to do it. Note that the POST model does not contain the Id property.
public class MyGetModel
{
[Required]
public string CanBeSetByConsumer { get; set; }
public int Id { get; set; }
}
public class MyPostModel
{
[Required]
public string CanBeSetByConsumer { get; set; }
}
public class MyModelController : ApiController
{
public MyGetModel Get(int id)
{
// get MyModel by Id
return new MyGetModel();
}
public MyGetModel Post(MyPostModel myData)
{
// save myData to a store and generate an ID
// return myGetData with ID populated with a 201 Created
}
}
Then if you have a lot of shared properties, you can have both of these inherit from an abstract class MyModel.
Another way to do it could be to add an action filter to the post action. In that action filter class, you would override the OnActionExecuting method, inspect the POST values collection for a value under the Id key, and set your 400 BAD REQUEST response there.
public class PreventIdValueAttribute
: System.Web.Http.Filters.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// check request for id value, and if present,
// set the result to a 400 bad request HttpResponseMessage
}
}
[PreventIdValue]
public MyModel Post(MyModel myData)
{
// save myData to a store and generate an ID
// return myData with ID populated with a 201 Created
}
Note that with the second option, your MyModel instance will still have an Id value in the Post action, but its value will be zero.

Related

StrawberryShake GraphQl Dto constructor can't be empty

I'm having some problem with one of my data in strawberry shake. It's a simple query but when I try to create an object of it, the generated code dtoData ask for a type in the constructor.
All my other query don't required anything in the constructor.
I use the Dto for model in the ViewModel for mapping.
Example of a working one.
public clas ProjectModelVM : ViewModelBase
{
//The ProjectDtoInput class is created by the Generation code.
// If I create a new Instance of ProjectDtoInput the constructor is empty and it's working
// var project = new ProjectDtoInput()
public ProjectDtoInput _projectModel;
public ProjectModelVM(ProjectDtoInput projectModel)
{
_projectModel = projectModel;
}
}
the query is
query GetActiveProject{
activeProject{
cL_PROJET_NO,
cL_PROJET_NOM
}
}
And the Dto in the server side is
public class QmProdFormDto
{
public int? FormID { get; set; }
public string? FormName { get; set; }
public string? ProjectNo { get; set; }
public string? ProjectName { get; set; }
public string? DivName { get; set; }
public string? SubDivName { get; set; }
}
Example of a NON working one
public class PaintBrandModelVM : ViewModelBase
{
// The QmProdPaintDtoData class is created by the Generation code.
// If I create a new Instance of QmProdPaintDtoData the constructor ask me for some __type input
public QmProdPaintDtoData _paintBrandModel;
public PaintBrandModelVM(QmProdPaintDtoData paintBrandModel)
{
_paintBrandModel = paintBrandModel;
}
}
the query is
query GetPaintLst{
paintLst{
paintBrandID,
paintBrandName
}
}
And the Dto on the server side is
public class QmProdPaintDto
{
public int? PaintBrandID { get; set; }
public string? PaintBrandName { get; set; }
}
I don't understand why one of the DTO ask me for mendatory input.
Can someone give me a hand on this.
Tell me if you need more of the code.
Thanks.
Jc

How to create a custom filter at .net core

I try one of the following without any success.
1. Validate a property's (username) value in the model binding with another property's value in the model (id, to find the user).
2. Validate all the sent model to a put request in the controller.
How can i create a custom filter to catch all the model to one of the property's value by use another value in the sent model?
You can use Fluent Validator in .NET Core for such validations
Step 1 :- Register it in the startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddFluentValidation(fvc =>
fvc.RegisterValidatorsFromAssemblyContaining<Startup>());
}
Step 2 :-
Define the validation rules like this
public class RegistrationViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
public class RegistrationViewModelValidator : AbstractValidator<RegistrationViewModel>
{
readonly IUserRepository _userRepo;
public RegistrationViewModelValidator(IUserRepository userReo)
{
RuleFor(reg => reg.FirstName).NotEmpty();
RuleFor(reg => reg.LastName).NotEmpty();
RuleFor(reg => reg.Email).NotEmpty();
RuleFor(reg => reg.FirstName).Must(DoesnotExist);
}
bool DoesnotExist(string userName)
{
return _userRepo.FindByUserName(userName) != null;
}
}
Step 3:-
IN the controllers
[HttpPost]
public IActionResult FormValidation(RegistrationViewModel model)
{
if (this.ModelState.IsValid) {
ViewBag.SuccessMessage = "Great!";
}
return View();
}
Refer this link for the complete documentation

ASP.NET Web API deep model binding

I've noticed (even in Web API 2.1) that deep parameter types get filled (processed by the model binder) only on the first level. That is :
public class Person
{
public string Name { get; set; }
public PersonDetails Details { get; set; }
}
public class PersonDetails
{
public string Address { get; set; }
public int Age { get; set; }
}
// ...
public class PersonController : ApiController
{
[HttpPost]
public void ProcessPerson(Person person)
{
// person.Name is filled in correctly
// person.Details.Address and person.Details.Age are not filled in correctly. That is, they have default values (null and 0)
}
}
Is there a simple solution for this problem, except flatting out the Person class like so ?
public class PersonData
{
public string Name { get; set; }
public string Address { get; set; }
public int Age { get; set; }
}
Later edit 1 :
If I flatten the Person class I get all the data correctly
The request is made by POST (and not GET) because I need to ensure there is no caching and since the operation alters state it would be semantically incorrect to use GET

FindBy method Entity framework

I have this account Model:
public class Account :IAggregateRoot
{
public Account()
{
}
public Account(Guid accountId)
{
Id = accountId;
}
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
and this repository class :
public class Repository<T> : IRepository<T> where T : class, IAggregateRoot
{
private readonly DbSet<T> _entitySet;
public T FindBy(T entity)
{
return _entitySet.Find(entity);
}
}
and now when I want to get an entity by Id , for example :
public AccountViewModel GetAccountBy(Guid accountId)
{
var account = new Account(accountId);
_unitOfWork.AccountRepository.FindBy(account);
var accountView = account.ConvertToAccountView();
return accountView;
}
I got this Error :
The specified parameter type is not valid. Only scalar types, such as System.Int32, System.Decimal, System.DateTime, and System.Guid, are supported.
my action to call GetAccountBy is like this:
public ActionResult Edit(Guid accountId)
{
var account = _accountService.GetAccountBy(accountId);
return View(account);
}
what is problem with this ? Any help is much appreciated.
You're not calling the DBSet.Find() method correctly.
As the documentation states you need to pass
The values of the primary key for the entity to be found
you don't pass in an instance of the entity, you pass in the key values that identify an entity. From your example you don't need to create a new instance of account:
var account = new Account(accountId);
_unitOfWork.AccountRepository.FindBy(account);
you just need to pass the accountId to FindBy()
_unitOfWork.AccountRepository.FindBy(accountId);
Here's your code amended:
public class Repository<T> : IRepository<T> where T : class, IAggregateRoot
{
private readonly DbSet<T> _entitySet;
public T FindBy(params Object[] keyValues)
{
return _entitySet.Find(keyValues)
}
}
public AccountViewModel GetAccountBy(Guid accountId)
{
_unitOfWork.AccountRepository.FindBy(accountId);
var accountView = account.ConvertToAccountView();
return accountView;
}
You can call the DbSet.Find(params object[] keyValues) Method only with System.Int32 and System.Guid as the error message indicates. (well and System.Decimal, System.DateTime probably for composite keys)
The method will not look for a Id or PK in your Model and use it automatically (when you pass Account, the method will not use Account.Id) - as its using "the primary key value" http://msdn.microsoft.com/en-us/library/gg696418(v=vs.103).aspx
Consider passing a predicate as suggested in FindBy Id method in EntityFramework
If you Models always have an Id of Type Guid, then maybe you can pass the Id directly:
public T FindBy(T entity)
{
return _entitySet.Find(entity.Id);
}
Hope this helps.

MvcSerializer().Deserialize(serialized,SerializationMode....)

In asp.net mvc 3.0, I want to Deserialize and object which is stored in an input hidden filed.
Here is the Serializable class :
[Serializable]
public class RegistrationData
{
public string Name { get; set; }
public string Email { get; set; }
}
and the this is how i generate hidden field :
<%: Html.Serialize("regData", Model,
Microsoft.Web.Mvc.SerializationMode.Signed)%>
but when I Deserialize it through following method, I get null values for my object :
var serialized = Request.Form["regData"];
if (serialized != null)
{
regData = (RegistrationData)newMvcSerializer().Deserialize(serialized,SerializationMode.Signed);
}
Can any one help me please?
Model
public class RegistrationData
{
public string Name { get; set; }
public string Email { get; set; }
}
View
#using (Html.BeginForm())
{
#Html.HiddenFor(m=>m.Name)
#Html.HiddenFor(m=>m.Email)
}
Controller
public ActionResult Foo(RegistrationData form)
{
//do
//form.Name ->> get name
}

Resources