Ive just started upgrading an old .net framework platform to .net Core 5 using Razor Pages
The first issue i ran into is on updating records.
We have a frontend and a backend form to edit users. On frontend only few fields are visible and on backend we have more fields
The model could look like this
public class User
{
public int ID {get;set;}
public string Name {get;set;}
public int UserType {get;set;}
public DateTime TStamp {get;set;}
}
On frontend the user can update the name, and i don't want to expose the value of UserType(or TStamp) using a hidden field.
But this means that the Usertype and TStamp always are reset
I have read the the best way is to send the model to the server and then update(and validate) the record serverside like :
Model recordToUpdate = GetRecordFromDB(id)
recordToUpdate.Name = postedRecord.Name;
UpdateRecord(recordToUpdate);
return recordToUpdate
Is there anyway else to accomplish update only few fields ?
08-02-2021 11:57
I have found this script which iterates through a model and a viewmodel and then transfer data.
https://www.codeproject.com/Tips/5163606/Generic-MVVM-Data-Exchange-between-Model-and-ViewM
public enum MVVMDirection { FROM, TO };
/// <summary>
/// ViewModel base class
/// </summary>
public class VMBase
{
/// <summary>
/// Move the data from the model to the viewmodel, using reflection.
/// Property names in both objects MUST be the same (both name and type)
/// </summary>
/// <typeparam name="TModel">The model's type</typeparam>
/// <param name="model">The model object the data will be moved from</param>
public void UpdateFromModel<TModel>(TModel model)
{
this.Update<TModel>(model, MVVMDirection.FROM);
}
/// <summary>
/// Move the data from the viewmodel to the model, using reflection.
/// Property names in both objects MUST be the same (both name and type)
/// </summary>
/// <typeparam name="TModel">The model's type</typeparam>
/// <param name="model">The model object the data will be moved from</param>
public void UpdateToModel<TModel>(TModel model)
{
this.Update<TModel>(model, MVVMDirection.TO);
}
/// <summary>
/// Update to or from the model based on the specified direction. Property names in both
/// objects MUST be the same (both name and type), but properties used just for the view
/// model aren't affected/used.
/// </summary>
/// <typeparam name="TModel">The model's type</typeparam>
/// <param name="model">The model object the data will be moved to/from</param>
/// <param name="direction">The direction in which the update will be performed</param>
public void Update<TModel>(TModel model, MVVMDirection direction)
{
PropertyInfo[] mProperties = model.GetType().GetProperties();
PropertyInfo[] vmProperties = this.GetType().GetProperties();
foreach (PropertyInfo mProperty in mProperties)
{
PropertyInfo vmProperty = this.GetType().GetProperty(mProperty.Name);
if (vmProperty != null)
{
if (vmProperty.PropertyType.Equals(mProperty.PropertyType))
{
if (direction == MVVMDirection.FROM)
{
vmProperty.SetValue(this, mProperty.GetValue(model));
}
else
{
vmProperty.SetValue(model, mProperty.GetValue(this));
}
}
}
}
}
If you are using EF Core, you can tell it which properties have to be updated in this way:
var user = new User{ ID = id, Name = postedRecord.Name};
_dbContext.Employee.Attach(user);
_dbContext.Entry(user).Property(x => x.Name).IsModified = true;
_dbContext.SaveChanges();
Related
I want to show request and response body as parameter not as raw data .
For that I have followed Generate Description Fields for Body Parameters answer but not get success , still showing
/// <summary>
/// Gets some very important data from the server.
/// </summary>
/// <param name="reqData">A Test Model</param>
[HttpPost]
public AccountService.GetAccountList_ResponseData GetAccountList([FromBody] AccountService.GetAccountList_ReqestData reqData)
{
}
//--my modal
public class GetAccountList_ReqestData
{
/// <summary>
/// StartDate
/// </summary>
/// <param name="StartDate">Describe parameter.</param>
public string StartDate { get; set; }
}
//---Also enabled
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/WebApi.xml")));
You have to add XmlDocs to your class GetAccountList_RequestData
/// <summary>
/// My requestData model
/// </summary>
/// <param name="reqData">The description you want </param>
public class GetAccountList_ReqestData
{
/// <summary>
/// StartDate
/// </summary>
/// <param name="StartDate">Describe parameter.</param>
public string StartDate { get; set; }
}
You followed the steps correctly, however the person asking in the other thread was displaying a property. You on the other hand are asking for an object, which is your class. Therefor the xmldocs of the class will be used to generate the descriptions.
In C# (Asp.Net Core) )I have the following documentation over a child object of type NameValue.
I wish to have the documentation I added here over FromWarehouse, but when Swagger renders, it uses the definition for NameValue, not FromWarehouse.
/// <summary>
/// The warehouse this transfer is coming from
/// </summary>
/// <remarks>GLW, WDI, PSO, SMA, SHW</remarks>
public NameValue FromWarehouse { get; set; }
You need to do 3 things:
The summary should be moved from class property to class.
not here
public NameValue FromWarehouse { get; set; }
but to
/// <summary>
/// The warehouse this transfer is coming from
/// </summary>
/// <remarks>GLW, WDI, PSO, SMA, SHW</remarks>
public class NameValue{
....
You need to add document generation, with Include Xml Comments
services.AddSwaggerGen(c =>
{
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
Add this to your .csproj file
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
This is the example I used
/// <summary>
/// The warehouse this transfer is coming from
/// </summary>
/// <remarks>GLW, WDI, PSO, SMA, SHW</remarks>
public class NameValue
{
/// <summary>
/// The Name
/// </summary>
public string Name { get; set; }
/// <summary>
/// The Value
/// </summary>
public string Value { get; set; }
}
Here is the result:
The remarks, I was not able to include the, but as far as I understand, remarks are used to provide payload example, and it is used on top of the resource method.
All this I use with dotnet 6 and Swashbuckle.AspNetCore Version 6.2.3.
I hope it helps.
Reference to Microsoft documentation.
I have an example input model as follows:
public class CarInputModel {
public string Name { get; set; }
public string ModelName { get; set; }
}
This values will come from UI, what kind of annotations can I use with swagger to describe this API model as much as possible?
You can't use many annotations at all to describe the model. You mostly describe the API itself
[HttpGet] and [HttpPost] for the http attributes
[Produces(typeof(CarInputModel)] for the return type of an action and [ProducesResponseType(typeof(CarInputModel), (int)HttpStatusCode.OK)] for result types based on http code (i.e. return different model on error)
[Route] for the route itself
Additionally you can use Xml Docs to describe the classes and their parameters.
/// <summary>
/// Adds a new car model.
/// </summary>
/// <param name="model">The model to add</param>
/// <response code="201">Returns when the car was added successfully and returns the location to the new resource</response>
/// <response code="400">Invalid Request data.</response>
/// <response code="409">Car mode already exists.</response>
/// <returns>The newly added model on success and a list of errors on failure.</returns>
[HttpPost]
[ProducesResponseType(typeof(CarInputModel), (int)HttpStatusCode.Created)]
[ProducesResponseType(typeof(SerializableError), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(void), (int)HttpStatusCode.Conflict)]
public IActionResult AddCar(CarInputModel model)
{
}
/// <summary>
/// Represents a car
/// </summary>
public class CarInputModel {
/// <summary>
/// Name of the car
/// </summary>
public string Name { get; set; }
/// <summary>
/// Model of the car
/// </summary>
public string ModelName { get; set; }
}
In order to use XmlDocs you need to enable compilation of the xml docs in your project settings (and the of your models) and then add this to your Startup.cs
services.AddSwaggerGen(options =>
{
var appEnv = PlatformServices.Default.Application;
options.IncludeXmlComments(Path.Combine(appEnv.ApplicationBasePath, $"{appEnv.ApplicationName}.xml"));
});
Code is further down in the post.
Question: I would like Swashbuckle to generate the following two "GET" requests:
1. mydomain.com/api/books?apikey=12345567891011121314151617181920
2. mydomain.com/api/books/1234?apikey=12345567891011121314151617181920
Swashbuckle does fine on #1 and it works great from the Swagger UI. But for #2, it ends up generating (and calling):
mydomain.com/api/books/{Id}?id=1234&apikey==12345567891011121314151617181920
Needless to say, that GET fails. Swashbuckle is picking up the literal "Id" from the route attribute as well as the BookDetail object. In fact, it shows both IDs in the UI, but I was able to solve that by de-duping by hooking up a custom IOperationFilter, but that obviously didn't help with correcting the actual GET request path.
I already looked at Duplicate parameter output in Swagger 2 but that answer does not work for me. I am looking for having Swashbuckle use the "Id" that's part of the BookDetail object so that Swagger UI shows the description (from the XML comment) while supporting route based ID rather than query string based: mydomain.com/api/books/1234.... What am I doing wrong? (I am on Swashbuckle 5.2.2).
Code:
/// <summary>
/// Books controller
/// </summary>
[RoutePrefix("api")]
public class BooksController : ApiController
{
/// <summary>
/// Returns books matching the search query
/// </summary>
/// <param name="searchRequest"></param>
/// <returns></returns>
[Route("books")]
public IHttpActionResult Get(BookSearch searchRequest)
{
//Do stuff with request
return Ok();
}
/// <summary>
/// Retruns a single book for the given ID
/// </summary>
/// <param name="detailRequest"></param>
/// <returns></returns>
[Route("books/{id:int}")]
public IHttpActionResult Get(BookDetail detailRequest)
{
//Do stuff with request
return Ok();
}
/// <summary>
/// Book search
/// </summary>
public class BookSearch
{
/// <summary>
/// API key
/// </summary>
[Required]
public string ApiKey { get; set; }
/// <summary>
/// Search terms
/// </summary>
public string Query { get; set; }
}
/// <summary>
/// Book detail
/// </summary>
public class BookDetail
{
/// <summary>
/// API key
/// </summary>
[Required]
public string ApiKey { get; set; }
/// <summary>
/// Book of the ID you want to retrieve
/// </summary>
[Required]
public int Id { get; set; }
/// <summary>
/// Boolean indicating whether to include photos or not
/// </summary>
public bool IncludePhotos { get; set; }
}
}
I have some data I need to store somewhere after a user logs in, what I need are:
1. A list of customers
2. US States list
for now that's all. Now Customer's list can continue to grow constantly and even after it has been loaded, obviously US States don't.
Currently what I do in my login.aspx page after the user logs in and everything is validated then I go to my SQL database and load all customers and US states into a Session variable. Right now this works although it does take a little bit of time but since there's only about 3 users at any time testing the site it doesn't affect. But as I understand this could be a problem when the company of about 50+ users start using it or even customers aswell.. So what would be the best way to work this and why? Thanks in advance.
ps. if this is not a valid question, please let me know and I'll take it down, I just didn't know where to ask this and get some useful feedback.
Andres,
Is it possible to (instead of using session) to cache the data. This can be done in multiple ways through various caching techniques (IE System.Web.Caching, MemoryCahce).
As your users grow \ shrink you can modify the DB and the cached instance at the same time. When the user requests the list of users (states) the cached list is evaluated. If the cached list isnt set then you re-build the cache list.
You could do something like. (Basic example)
public class UserCache : IEnumerable<User>
{
/// <summary>
/// const cache string
/// </summary>
const string userCacheString = "_userCacheList";
/// <summary>
/// current list of users
/// </summary>
public static UserCache Current
{
get
{
if (HttpContext.Current == null)
throw new Exception("NO CONTEXT");
var userList = HttpContext.Current.Cache[userCacheString] as UserCache;
if (userList == null)
{
userList = new UserCache();
HttpContext.Current.Cache[userCacheString] = new UserCache();
}
return userList;
}
}
/// <summary>
/// default constructor
/// </summary>
public UserCache()
{
}
/// <summary>
/// the list of users
/// </summary>
List<User> users;
/// <summary>
/// adds a user
/// </summary>
/// <param name="user"></param>
public void Add(User user)
{
if (this.Contains(user))
return;
this.users.Add(user);
}
/// <summary>
/// removes a user
/// </summary>
/// <param name="user"></param>
public void Remove(User user)
{
if (this.Contains(user))
return;
this.users.Remove(user);
}
/// <summary>
/// clears a user
/// </summary>
public void Clear()
{
this.users = null;
}
/// <summary>
/// fills the users from the database
/// </summary>
void fillUsers()
{
this.users = new List<User>();
//TODO: Get from DB
}
/// <summary>
/// gets the enumerator
/// </summary>
/// <returns></returns>
public IEnumerator<User> GetEnumerator()
{
if (this.users == null)
fillUsers();
foreach (var user in users)
yield return user;
}
/// <summary>
/// gets the enumerator
/// </summary>
/// <returns></returns>
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public class User
{
public string UserName { get; set; }
public Guid UserID { get; set; }
public string Email { get; set; }
}
From here you user management (where you add \ remove users) can call the UserCache to modify its collection accordinly. Such as (psudo code)
public class UserManager
{
public void Register(string userName, string email)
{
//TODO: Register in DB.
UserCache.Current.Add(new User
{
UserID = Guid.NewGuid(),
Email = email,
UserName = userName
});
}
}
From here you can always call UserCache.Current to get the current user list.
Just a thought.
EDIT: Response to Simon Halsey comment.
In the example I did inherit from the IEnumerable<> interface and not the List<> interface. This was a personal preference and to support "how" the class was defined. Now before I explain, this is not the only way but just a conceptual way of achieving the result.
In the example the method Clear() clears the inner list by setting the inner list user to null. In the IEnumerable<> implentation GetEnumerator() method the first check is if the inner list is null. If the inner list is null then the fillUsers() method is called to retrieve all users from the database.
If this example inherited from List<> then the Clear() method of List<> would be called and clears the list (removing all items) however the list is not null. Therefore enumerating the list after the Clear() method has been called will result in no users. Now this could be re-written using a List<> implentation as follows. Where the only thing you will have to do is override the Clear() method and the constructor to load the users. Such as.
public class UserListCache : List<User>
{
/// <summary>
/// const cache string
/// </summary>
const string userCacheString = "_userCacheList";
/// <summary>
/// current list of users
/// </summary>
public static UserListCache Current
{
get
{
if (HttpContext.Current == null)
throw new Exception("NO CONTEXT");
var userList = HttpContext.Current.Cache[userCacheString] as UserListCache;
if (userList == null)
{
userList = new UserListCache();
HttpContext.Current.Cache[userCacheString] = new UserListCache();
}
return userList;
}
}
/// <summary>
/// default constructor
/// </summary>
public UserListCache()
{
this.fillUsers();
}
/// <summary>
/// clear the list
/// </summary>
public new void Clear()
{
base.Clear();
this.fillUsers();
}
/// <summary>
/// fills the users from the database
/// </summary>
void fillUsers()
{
//TODO: Get from DB
}
}
Now neither method is better than the other (and the solution may not be adequate).