I created a function that add a new user with Angular/WebAPI , so in angular i send the object like this :
AddUser(person : person):Observable<person>
{
return this.http.post<person>(this.baseUrl,person);
}
and in WebAPI i got the data :
[HttpPost]
public async Task<ActionResult<Person>> PostPerson(Person person)
{
Now I want to add a picture of each user, and i don't know how it's gonna be the objet in angular, should i add a property imageProfile as File which i'm not sure if it's possible , or it should be a string of the uploaded file.
export class person {
idPerson:number=0;
fname : string ="" ;
lname : string ="";
password : string ="";
idCategory? :number;
nameCategory:string="";
imageProfile :
}
That's very good info from Codemaze, as always. The remark from Lavstsen is not correct, there's no need for a separate call.
For example, you could have a form linked to the structure of the person-type:
initializeForm() {
this.personForm = this.form.group({
fname: [''],
})
}
In your onSubmit-method, you seize the date from your form and put in a dto, in this example through a getPersonDto-method
private getPersonDto(): PersonDto {
let dto = new PersonDto();
dto.fname = this.imageForm.controls.fname.value;
return dto;
}
and next you can solve it like this:
const formData = new FormData();
formData.append('Imagefile', this.selectedFile, this.selectedFile.name);
formData.append('fname', personDto.fname); // needs to be strings => toString()
this.personApiClient.uploadImageFile(this.personId, formData)
.subscribe((res: any) => {
this.uploadOrDeleteResults = res;
this.showToastrMessageSuccess("PAGES.COMMON.TOASTR.TITLE.SUCCESSFULCREATE", "PAGES.COMMON.TOASTR.MESSAGE.SUCCESSFULCREATE");
},
(error) => {
this.crudHasErrors = true;
this.errorHttpErrorResponse = error;
this.errors = this.errorHttpErrorResponse.error;
},
you don't need the dto-step as such, but if you structurally always use dto's in your application, it would make more sense
Take care and good luck
I have another workaround but this will only work for very small files where you don't need upload percentage to show on UI.
I have my controller like this:
[HttpPost("/api/sign-up")]
public void SaveUser([FromForm] UserModel info)
{
_logger.LogDebug(null, "Hello", info); // Just to see contents of info object in debugger
}
and make your model like this:
public class UserModel
{
[FromForm(Name = "avatar")]
public IFormFile Avatar { get; set; }
[FromForm(Name = "email")]
public string Email { get; set; }
}
Related
I am making my way through various todo list tutorials while learning react and entity framework. As some background I have made my way though Microsoft's todo list todo tutorial; although I have replaced the front end part of that with my own front end. It was all working fine, until I've tried to extend it and hit the issue I will outline below.
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc). This is shown in the code below.
public class TodoItem
{
public long id { get; private set; }
public string title { get; private set; }
public bool IsComplete { get; private set; }
// Define constructor
public TodoItem(long newId, string newTitle)
{
id = newId;
title = newTitle;
IsComplete = false;
}
public void ToggleComplete()
{
IsComplete = !IsComplete;
}
}
The post action from the controller is shown below. I have included some debug printouts as these show where the field is already showing the title as null.
I believe this is the section of code I am struggling with and would like to know what mistakes I am making or what the best practices are!
[HttpPost]
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem item)
{
// returns null if model field set to private
System.Diagnostics.Debug.WriteLine("item title: " + item.title);
// Create new item passing in arguments for constructor
TodoItem newItem = new TodoItem(item.id, item.title);
_context.TodoItems.Add(newItem);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetTodoItem), new { id = newItem.id }, newItem);
}
The frontend method (js) where the post request is made is shown below:
const addTodoMethod = (title) => {
// Create new item
const item = {
title: title,
id: Date.now(),
isComplete: false,
}
// Update state
const newTodos = [...todos, item];
setTodos(newTodos);
// Can use POST requiest to add to db
axios.post('https://localhost:44371/api/todo/',
item)
.then(res=> {
console.log("Added item. Title: ", title);
})
.catch(function (error) {
console.log(error);
})}
I hope I've explained the problem well enough. Let me know if there is anything else needed!
I have updated the EF model to include private set fields for the added benefits (becoming read only after it is initialised etc).
There are two problems in what you did. The first one is that the Models must have a parameter-less constructor, and the second one that the properties must be public, both getter and setter.
The best you can do right now is to stop using your database entity for user input and create a ViewModel class:
public class TodoItemViewModel
{
public long id { get; set; }
public string title { get; set; }
public bool IsComplete { get; set; }
}
public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItemViewModel model)
{
var item = new TodoItem(item.id, item.title);
...
}
In this scenario:
var xhr = new XMLHttpRequest();
var fd = new FormData();
fd.append("file", document.getElementById('fileUpload').files[0]);
fd.append("PhoneId", '1234');
xhr.open("POST", '/Main/Upload', true);
xhr.send(fd);
xhr.addEventListener('load', function (event) {
var test = event.target.response;
})
I have a file upload along with an integer attached. My controller is:
public ActionResult Upload(Model newModel)
{
NewFiles files = new NewFiles ();
try
{
HttpPostedFileBase file = Request.Files[0];
}
catch(Exception)
{}
}
public class newModel
{
public int FileID { get; set; }
public string ReturnAction { get; set; }
public HttpPostedFileBase fileUpload { get; set; }
public int PhoneId { get; set; }
}
The uploaded file gets recognized, but what do I need in C# to view the uploaded PhoneId?
Your syntax is a bit off in the controller and won't compile. Changing the controller to the following syntax should work. This shows how to access the POSTed file and the model properties.
[HttpPost]
public ActionResult Upload(newModel model, HttpPostedFileBase file)
{
// Check the file is valid.
if (file == null || file.ContentLength == 0 || string.IsNullOrEmpty(file.FileName))
ModelState.AddModelError("fileUpload", "Invalid file uploaded.");
// Access model properties as you wish, like this:
int phoneID = model.PhoneId;
return null;
}
A couple of extra things to improve your code:
Generally speaking, when defining a class, the name of it should be in the format of CapitalizeEachWord.
It would be a lot easier if you used jQuery for your AJAX request rather than the normal JavaScript way of doing it. By using the normal JavaScript way, you're having to manually assign form properties to a FormData object, wheras with jQuery you can simpily do $("#formID").serialize().
it will work
string phoneId = Request.Form.Get("PhoneId");
When a path is named...
[Route("api/[controller]/{id}/Existing", Name = "ExistingOrdersLink")]
public class OrdersController : Controller {...}
...it is easy to create an absolute path using its name:
var address = Url.Link("ExistingOrdersLink", new { id = 22 });
However, I need the ability to generate a relative path from a Route name. So I write code like this:
//Is there a built-in mechanism for this?
string partial = new Uri(Url.Link("ExistingOrdersLink", new { id = id })).PathAndQuery;
The above works but, in ASP.NET Core, is there a more direct way to get the same result?
Context
Why do I need this? Some of the controllers I make are actually generic:
[Route("api/Subscribers/{id}/[controller]", "SubscriberLink")]
[Route("api/Organization/{id}/[controller]", "OrganizationLink")]
public class OrdersController<T> : Controller where T : IOrder
{
private ICommand<Order<T>> createOrder;
protected OrdersController(ICommand<Order<T>> createOrder)
{
this.createOrder = createOrder;
}
[HttpPost("{otype}")]
public async Task<IActionResult> Create(
Guid id,
[FromBody] Order<T> order)
{
order.CustomerOrderId = Guid.NewGuid();
createOrder.Handle(order);
string[] segs = Request.Path.Value.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries);
//This will become either "SubscriberLink" or "OrganizationLink"
string route = $"{segs[1]}Link";
//Is there a built-in mechanism for this?
string partial = new Uri(Url.Link(route, new { id = id })).PathAndQuery;
partial = $"{partial}/{order.CustomerOrderId}";
CreatedResult reply = Created(partial, null);
return reply;
}
}
I think you are looking for Url.RouteUrl
Example:
Url.RouteUrl("ExistingOrdersLink", new { id = 22});
I have a problem with my web api application. I get an internal error 500 when i try to post(save) a new user in my db.
The function bellow is the one that i use to make the client call.
public void InsertNewUser(RegisterModel pNewUser, string pEmail)
{
// Build rest uri
string lREST_Uri_Browse = string.Format(#"api/accountapi/saveuserdata"
// User data
/*pModelSerialized*/);
// Complete URI
string lREST_Uri = Helpers_API.endPoint + lREST_Uri_Browse;
var client = new HttpClient();
client.BaseAddress = new Uri(Helpers_API.endPoint);
var newUser = new Models.Models_API.Users
{
Email = pNewUser.Email,
FName = pNewUser.FName,
LName = pNewUser.LName,
Inserted = DateTime.Now,
ActiveAcc = true,
AccType = pNewUser.AccType,
BCompanyID = pNewUser.CompanyID,
PID = pNewUser.PID,
Password = pNewUser.Password,
Token = GetToken(pEmail),
ThirdParty = 0,
Gender = pNewUser.Gender,
BirthDate = pNewUser.BirthDate
};
// Add an Accept header for JSON format.
//client.DefaultRequestHeaders.Accept.Add(
// new MediaTypeWithQualityHeaderValue("application/json"));
// Create the JSON formatter.
MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
// Use the JSON formatter to create the content of the request body.
HttpContent content = new ObjectContent<Models.Models_API.Users>(newUser, jsonFormatter);
var result = client.PostAsync(lREST_Uri_Browse, content).Result;
}
This is the model
public class Users
{
public int BrokerID { get; set; }
public DateTime Inserted { get; set; }
public string Email { get; set; }
public string FName { get; set; }
public string LName { get; set; }
public bool ActiveAcc { get; set; }
public int BCompanyID { get; set; }
public int PID { get; set; }
public int AccType { get; set; }
public string Password { get; set; }
public string Token { get; set; }
public int Gender { get; set; }
public DateTime BirthDate { get; set; }
public int ThirdParty { get; set; }
}
And bellow is the POST in APIController:
public HttpResponseMessage SaveUserData(Users pNewUser)
{
bool createUser = false;
// First check for provided email in DB
Users existingUser = asigCtx.Users.Where(u => u.Email == pNewUser.Email).SingleOrDefault();
if (existingUser == null)
createUser = true;
else if (existingUser.ActiveAcc)
createUser = true;
if (createUser)
{
using (asigCtx = new AsigPrimeContext())
{
Users user = new Users()
{
Email = pNewUser.Email,
FName = pNewUser.FName,
LName = pNewUser.LName,
Inserted = DateTime.Now,
ActiveAcc = true,
AccType = pNewUser.AccType,
BCompanyID = pNewUser.BCompanyID,
PID = pNewUser.PID,
Password = pNewUser.Password,
Token = pNewUser.Token,
ThirdParty = 0,
Gender = pNewUser.Gender,
BirthDate = pNewUser.BirthDate,
};
asigCtx.Users.Add(user);
asigCtx.SaveChanges();
}
}
var response = Request.CreateResponse<Users>(HttpStatusCode.Created, pNewUser);
return response;
}
Can anyone give me piece of advice with this code because i'm new in this and i just want to do it wright. TNX
You have an error in your code. A 500 error indicates that your code contains an unhandled exception that killed its worker process.
Change your web.config file so that your application outputs the full error message.
A few things I would check/try to get to the bottom of the issue:
Is the code above exactly the same as in your application or have you changed anything (even if only to make it simpler)?
Is the Users object used in SaveUserData controller method definitely from the same assembly as the one that you are posting from the InsertNewUser method?
Is the Users object complete on the listing (e.g. does it have any constructors)?
Have you tried executing the request to the endpoint through fiddler? This way you take any potential bugs in the client call out of the equation to see if the controller method on its own works.
I've noticed this line:
string lREST_Uri_Browse = string.Format(#"api/accountapi/saveuserdata"
// User data
/*pModelSerialized*/);
Are you formatting the url and passing any params to it? If so, what are the params and what does your WebApi route look like?
That should be enough for a start - let me know how you get on.
BTW: Two things that strike me in your code (unrelated to the question):
It's very confusing to have a class called 'Users' representing a single user. If it's you're code I'd advise to change that to singular.
the properties on the Users object are using abbreviations - I don't think it's that expensive to spell them out and I can guarantee you that anyone new to this code will be grateful if you put a full name rather than a mysterious BCompanyID, or less mysterious but still hard to read (and write for that matter) FName
I had the same problem a few weeks ago. After doing a few tests, I've realized that my WebApi application was using DataContractJsonSerializer by default, instead of Newtonsoft. To fix it, you can change the default serializer on your server to NewtonSoft or to use DatacontractJsonSerializer to serialize your object and pass it to HttpWebRequest.
I would use jQuery to post some Json to the same URL ($.post(#"api/accountapi/saveuserdata", { Email: "e#example.com", FName = "Vlad" ... })), thus eliminating InsertNewUser from the equation. If you still get a null pNewUser parameter in SaveNewuser, then I would look at your API's route configuration to make sure the server expects SaveUserData(Users). If you get a non-null pNewUser from jQuery, then I would look closer at the output from InsertNewUser. If I had to bet, I would put my money on the routing configuration.
it seemed that i had an error with the LINQ code
Users existingUser = asigCtx.Users.Where(u => u.Email == pNewUser.Email).SingleOrDefault();
After i solved this the request was ok.
Tnx for the help :) Appreciate.
(I realize this question is very similar to How to whitelist/blacklist child object fields in the ModelBinder/UpdateModel method? but my situation is slightly different and there may be a better solution available now that wasn't then.)
Our company sells web-based software that is extremely configurable by the end-user. The nature of this flexibility means that we must do a number of things at run time that would normally be done at compile time.
There are some rather complex rules regarding who has read or read/write access to most everything.
For instance, take this model that we would like to create:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace j6.Business.Site.Models
{
public class ModelBindModel
{
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string FirstName { get; set; }
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string MiddleName { get; set; }
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string LastName { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSalary, WriteAccess = User.CanWriteSalary)]
public string Salary { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSsn, WriteAccess = User.CanWriteSsn)]
public string Ssn { get; set; }
[Required]
public string SirNotAppearingOnThisPage { get; set; }
}
}
In the controller, it is not difficult to "unbind" things manually.
var resetValue = null;
modelState.Remove(field);
pi = model.GetType().GetProperty(field);
if (pi == null)
{
throw new Exception("An exception occured in ModelHelper.RemoveUnwanted. Field " +
field +
" does not exist in the model " + model.GetType().FullName);
}
// Set the default value.
pi.SetValue(model, resetValue, null);
Using HTML helpers, I can easily access the model metadata and suppress rendering of any fields the user does not have access to.
The kicker: I can't figure out how to access the model metadata anywhere in the CONTROLLER itself to prevent over-posting.
Note that using [Bind(Include...)] is not a functional solution, at least not without additional support. The properties to Include are run-time (not compile time) dependent, and excluding the property does not remove it from the validation.
ViewData.Model is null
ViewData.ModelMetaData is null
[AllowAnonymous]
[HttpPost]
// [Bind(Exclude = "Dummy1" + ",Dummy2")]
public ViewResult Index(ModelBindModel dto)
{
zzz.ModelHelper.RemoveUnwanted(ModelState, dto, new string[] {"Salary", "Ssn"});
ViewBag.Method = "Post";
if (!ModelState.IsValid)
{
return View(dto);
}
return View(dto);
}
Any suggestions on how to access the Model MetaData from the controller? Or a better way to whitelist properties at run time?
Update:
I borrowed a page from this rather excellent resource:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=687
With a model that looks like this:
[Required]
[WhiteList(ReadAccessRule = "Nope", WriteAccessRule = "Nope")]
public string FirstName { get; set; }
[Required]
[WhiteList(ReadAccessRule = "Database.CanRead.Key", WriteAccessRule = "Database.CanWrite.Key")]
public string LastName { get; set; }
The class:
public class WhiteList : Attribute
{
public string ReadAccessRule { get; set; }
public string WriteAccessRule { get; set; }
public Dictionary<string, object> OptionalAttributes()
{
var options = new Dictionary<string, object>();
var canRead = false;
if (ReadAccessRule != "")
{
options.Add("readaccessrule", ReadAccessRule);
}
if (WriteAccessRule != "")
{
options.Add("writeaccessrule", WriteAccessRule);
}
if (ReadAccessRule == "Database.CanRead.Key")
{
canRead = true;
}
options.Add("canread", canRead);
options.Add("always", "be there");
return options;
}
}
And adding these lines to the MetadataProvider class mentioned in the link:
var whiteListValues = attributes.OfType<WhiteList>().FirstOrDefault();
if (whiteListValues != null)
{
metadata.AdditionalValues.Add("WhiteList", whiteListValues.OptionalAttributes());
}
Finally, the heart of the system:
public static void DemandFieldAuthorization<T>(ModelStateDictionary modelState, T model)
{
var metaData = ModelMetadataProviders
.Current
.GetMetadataForType(null, model.GetType());
var props = model.GetType().GetProperties();
foreach (var p in metaData.Properties)
{
if (p.AdditionalValues.ContainsKey("WhiteList"))
{
var whiteListDictionary = (Dictionary<string, object>) p.AdditionalValues["WhiteList"];
var key = "canread";
if (whiteListDictionary.ContainsKey(key))
{
var value = (bool) whiteListDictionary[key];
if (!value)
{
RemoveUnwanted(modelState, model, p.PropertyName);
}
}
}
}
}
To recap my interpretation of your question:
Field access is dynamic; some users may be able to write to a field and some may not.
You have a solution to control this in the view.
You want to prevent a malicious form submission from sending restricted properties, which the model binder will then assign to your model.
Perhaps something like this?
// control general access to the method with attributes
[HttpPost, SomeOtherAttributes]
public ViewResult Edit( Foo model ){
// presumably, you must know the user to apply permissions?
DemandFieldAuthorization( model, user );
// if the prior call didn't throw, continue as usual
if (!ModelState.IsValid){
return View(dto);
}
return View(dto);
}
private void DemandFieldAuthorization<T>( T model, User user ){
// read the model's property metadata
// check the user's permissions
// check the actual POST message
// throw if unauthorized
}
I wrote an extension method a year or so ago that has stood me in good stead a couple of times since. I hope this is of some help, despite not being perhaps the full solution for you. It essentially only allows validation on the fields that have been present on the form sent to the controller:
internal static void ValidateOnlyIncomingFields(this ModelStateDictionary modelStateDictionary, FormCollection formCollection)
{
IEnumerable<string> keysWithNoIncomingValue = null;
IValueProvider valueProvider = null;
try
{
// Transform into a value provider for linq/iteration.
valueProvider = formCollection.ToValueProvider();
// Get all validation keys from the model that haven't just been on screen...
keysWithNoIncomingValue = modelStateDictionary.Keys.Where(keyString => !valueProvider.ContainsPrefix(keyString));
// ...and clear them.
foreach (string errorKey in keysWithNoIncomingValue)
modelStateDictionary[errorKey].Errors.Clear();
}
catch (Exception exception)
{
Functions.LogError(exception);
}
}
Usage:
ModelState.ValidateOnlyIncomingFields(formCollection);
And you'll need a FormCollection parameter on your ActionResult declaration, of course:
public ActionResult MyAction (FormCollection formCollection) {