How to implement growing object with a design pattern? - asp.net-core

I want to design a response and design it by responses in my asp.net core application. The simlpe response is like following.
public class Response {
public string Status { get; set; } => "Ok";
public BaseReport BaseReport { get;set;}
}
if user sends extra parameters to my service, I want to change my response content dynamically.
public class ReportsController : ControllerBase
{
[HttpGet]
public ActionResult<Response> GetReport(bool isEmployee, bool isFinanace, bool isInformatinTech)
{
// if all parameters fals, return base report.
var report = baseReposrService.Get();
var response = new Response() { BaseReport = report };
if(isEmployee)
{
var ereport = employeeService.Get();
var response = new Response() {
BaseReport = report,
EmployeeReport = ereport
};
}
if(isFinanace)
{
var freport = financeService.Get();
var response = new Response() {
BaseReport = report,
EmployeeReport = freport
};
}
...
...
}
}
the response object is growing by query parameters.
So, is implementing the decorator pattern for this problem right way? Is there any best practice for these type problems?

I tried as below:
public class Response
{
public Response()
{
BaseReport = new BaseReport();
}
public string Status { get; set; } ="Ok";
public BaseReport BaseReport { get; set; }
}
public class BaseReport
{
public string report { get; set; }
public string reportcontent { get; set; }
}
public interface IResponseFactory
{
IResponseFactory Add(string key);
Response Create(string key);
}
public class ResponseFactory1 : IResponseFactory
{
private readonly Dictionary<string, Response> _responsedic = new Dictionary<string, Response>();
public ResponseFactory1()
{
this.Add("Employee").Add("Finanace").Add("InformatinTech");
}
public IResponseFactory Add(string key)
{
var response = new Response();
response.BaseReport.report = key;
response.BaseReport.reportcontent = key + "content";
_responsedic.Add(key, response);
return this;
}
public Response Create(string responsename)
{
if (_responsedic.ContainsKey(responsename))
{
return _responsedic[responsename];
}
else
{
return new Response() { BaseReport = new BaseReport() { report = "basereport",reportcontent= "basereportcontent" } };
}
}
in startup class:
services.AddSingleton<IResponseFactory, ResponseFactory1>();
in controller:
[HttpGet]
public ActionResult<Response> GetReport(string responsetype)
{
var response = _responseFactory.Create(responsetype);
return response;
}

Related

.net core GraphQL, GraphQL.SystemTextJson: Serialization and deserialization of 'System.Type' instances are not supported

In a ASP.NET core 5 application, I use GraphQL with GraphQL.SystemTextJson.
When I attempt to return a result, I get s System.NotSupportedException saying "Serialization and deserialization of 'System.Type' instances are not supported and should be avoided since they can lead to security issues.".
I suspect something to be missing in the configuration of DocumentWriter.
It is configured like this in ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
...
services.AddScoped<IDocumentWriter, DocumentWriter>();
Any suggestion?
Update:
for completeness, as asked by #AndrewSilver, I report the whole code (adapted from https://www.red-gate.com/simple-talk/dotnet/net-development/building-and-consuming-graphql-api-in-asp-net-core-3-1/ and ported to .net core 5.0).
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "GraphQlExperiments", Version = "v1" });
});
services.AddScoped<IDocumentExecuter, DocumentExecuter>();
services.AddScoped<IDocumentWriter, DocumentWriter>();
services.AddScoped<AuthorService>();
services.AddScoped<AuthorRepository>();
services.AddScoped<AuthorQuery>();
services.AddScoped<AuthorType>();
services.AddScoped<BlogPostType>();
services.AddScoped<ISchema, GraphQLDemoSchema>();
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GraphQlExperiments v1"));
}
// See: https://github.com/JosephWoodward/graphiql-dotnet
app.UseGraphiQl("/graphiql", "/api/v1/graphql");
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
public class Author
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class BlogPost
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Author Author { get; set; }
}
public class AuthorType : ObjectGraphType<Author>
{
public AuthorType()
{
Name = "Author";
Field(_ => _.Id).Description("Author's Id.");
Field(_ => _.FirstName).Description("First name of the author");
Field(_ => _.LastName).Description("Last name of the author");
}
}
public class BlogPostType : ObjectGraphType<BlogPost>
{
public BlogPostType()
{
Name = "BlogPost";
Field(_ => _.Id, type:
typeof(IdGraphType)).Description("The Id of the Blog post.");
Field(_ => _.Title).Description("The title of the blog post.");
Field(_ => _.Content).Description("The content of the blog post.");
}
}
public class AuthorQuery : ObjectGraphType
{
public AuthorQuery(AuthorService authorService)
{
int id = 0;
Field<ListGraphType<AuthorType>>(
name: "authors",
resolve: context =>
{
return authorService.GetAllAuthors();
});
Field<AuthorType>(
name: "author",
arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
resolve: context =>
{
id = context.GetArgument<int>("id");
return authorService.GetAuthorById(id);
}
);
Field<ListGraphType<BlogPostType>>(
name: "blogs",
arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "id" }),
resolve: context =>
{
return authorService.GetPostsByAuthor(id);
}
);
}
}
public class GraphQLQueryDTO
{
public string OperationName { get; set; }
public string NamedQuery { get; set; }
public string Query { get; set; }
public string Variables { get; set; }
}
public class GraphQLDemoSchema : Schema, ISchema
{
public GraphQLDemoSchema(IServiceProvider resolver) : base(resolver)
{
Query = resolver.GetService<AuthorQuery>();
}
}
public class AuthorService
{
private readonly AuthorRepository _authorRepository;
public AuthorService(AuthorRepository
authorRepository)
{
_authorRepository = authorRepository;
}
public List<Author> GetAllAuthors()
{
return _authorRepository.GetAllAuthors();
}
public Author GetAuthorById(int id)
{
return _authorRepository.GetAuthorById(id);
}
public List<BlogPost> GetPostsByAuthor(int id)
{
return _authorRepository.GetPostsByAuthor(id);
}
}
public class AuthorRepository
{
private readonly List<Author> authors = new List<Author>();
private readonly List<BlogPost> posts = new List<BlogPost>();
public AuthorRepository()
{
Author author1 = new Author
{
Id = 1,
FirstName = "Joydip",
LastName = "Kanjilal"
};
Author author2 = new Author
{
Id = 2,
FirstName = "Steve",
LastName = "Smith"
};
BlogPost csharp = new BlogPost
{
Id = 1,
Title = "Mastering C#",
Content = "This is a series of articles on C#.",
Author = author1
};
BlogPost java = new BlogPost
{
Id = 2,
Title = "Mastering Java",
Content = "This is a series of articles on Java",
Author = author1
};
posts.Add(csharp);
posts.Add(java);
authors.Add(author1);
authors.Add(author2);
}
public List<Author> GetAllAuthors()
{
return this.authors;
}
public Author GetAuthorById(int id)
{
return authors.Where(author => author.Id == id).FirstOrDefault<Author>();
}
public List<BlogPost> GetPostsByAuthor(int id)
{
return posts.Where(post => post.Author.Id == id).ToList<BlogPost>();
}
}
[Route("/api/v1/graphql")]
public class GraphQLController : Controller
{
private readonly ISchema _schema;
private readonly IDocumentExecuter _executer;
public GraphQLController(
ISchema schema,
IDocumentExecuter executer
)
{
_schema = schema;
_executer = executer;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody] GraphQLQueryDTO query)
{
var result = await _executer.ExecuteAsync(_ =>
{
_.Schema = _schema;
_.Query = query.Query;
_.Inputs = query.Variables?.ToInputs();
});
if (result.Errors?.Count > 0)
{
return BadRequest();
}
return Ok(result.Data);
}
}
And this is a sample request that triggers the error:
query {
author (id: 1){
id
firstName
lastName
}
blogs
{
id
title
content
}
}
I solved creating a custom JsonConverter:
public class CustomJsonConverterForType : JsonConverter<Type>
{
public override Type Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options
)
{
// Caution: Deserialization of type instances like this
// is not recommended and should be avoided
// since it can lead to potential security issues.
// If you really want this supported (for instance if the JSON input is trusted):
// string assemblyQualifiedName = reader.GetString();
// return Type.GetType(assemblyQualifiedName);
throw new NotSupportedException();
}
public override void Write(
Utf8JsonWriter writer,
Type value,
JsonSerializerOptions options
)
{
string assemblyQualifiedName = value.AssemblyQualifiedName;
// Use this with caution, since you are disclosing type information.
writer.WriteStringValue(assemblyQualifiedName);
}
}
Then, in configureServices:
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.WriteIndented = true;
options.JsonSerializerOptions.Converters.Add(new CustomJsonConverterForType());
});
Instead of using System.Text.Json.JsonSearializer Use NewtonSoft.JsonConvert.SearializeObject
I fixed that problem by using the snippet shown in the docs: https://graphql-dotnet.github.io/docs/migrations/migration3
[HttpPost]
public async Task<IActionResult> Post([FromBody] GraphQLQueryDTO query)
{
var result = await _executer.ExecuteAsync(_ =>
{
_.Schema = _schema;
_.Query = query.Query;
_.Inputs = query.Variables?.ToInputs();
});
/* ----------- Added this ---------------------------------*/
HttpContext.Response.ContentType = "application/json";
HttpContext.Response.StatusCode = 200; // OK
var writer = new GraphQL.SystemTextJson.DocumentWriter();
await writer.WriteAsync(HttpContext.Response.Body, result);*
/* ------------------------------------------------------*/
if (result.Errors?.Count > 0)
{
return BadRequest();
}
return Ok(result.Data);
}
}
In your startup.cs, in ConfigureServices
Add AddNewtonsoftJson() after AddControllers()
services.AddControllers().AddNewtonsoftJson();

getting 400 error on webapi call blazorserver

i am trying to setup a blazor server app, calling a webapi.
I keep getting a 400 error returned, when I call the API.
I have 3 Projects, projectserver and projectapi. projectserver is where the Blazor app sits and Project API is where the API sits.
I don't know if the apicall can find the API as it does not hit any breakpoints in the API section, I am totally confused, as if it cannot find the API then it should return a 404 or other error and not 400 ?
thank you for your efforts.
this is my code,
Projectserver, this is where I post the Register Model to the API
public string message { get; set; }
public RegisterModel r = new RegisterModel();
private async Task Create(MouseEventArgs e)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(r);
var client = clientfactory.CreateClient("ServerApi");
var result = await client.PostAsJsonAsync("/Account/Register",json); // check the Startup file and check base address for the Full route.
message = result.StatusCode.ToString();
}
}
the ClientFactory returns the base address of what is defined in startup.cs
services.AddHttpClient("ServerApi", client => client.BaseAddress = new Uri("https://localhost:44302/"));
the API is Projectserver and defined as follows.
[Route("[controller]")]
[ApiController]
public class AccountContoller : ControllerBase
{
private readonly ApplicationDbContext _context;
private readonly SecurityOptions _securityOptions;
private readonly JwtIssuerOptions _jwtOptions;
// GET: api/<Account>
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<Account>/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/<Account>
[HttpPost]
public void Post([FromBody] string value)
{
}
// POST api/<Account>
[HttpPost("Register")]
public async Task<ActionResult<RegisterResult>> Register(RegisterModel model)
{
RegisterResult r = new RegisterResult();
var Exisits = await _context.Users.Where(r => r.EmailAddress == model.Email).FirstOrDefaultAsync();
if(Exisits != null)
{
r.Sucsess = false;
r.ErrorMessage = "Email - Already Exisits";
return r;
}
else
{
try
{
User newuser = new User();
newuser.CreatedDateTime = DateTime.UtcNow;
newuser.UserID = Guid.NewGuid();
newuser.MobileNumber = model.MobileNumber;
newuser.Password = model.Password;
newuser.FirstName = model.FirstName;
newuser.Surname = model.LastName;
_context.Users.Add(newuser);
await _context.SaveChangesAsync();
r.Sucsess = true;
return r;
}
catch(Exception e)
{
r.Sucsess = false;
r.ErrorMessage = e.ToString();
return r;
}
}
}
the Model classes are defined as Serializable
[Serializable]
public class RegisterResult
{
public bool Sucsess { get; set; }
public string ErrorMessage { get; set; }
}
[Serializable]
public class RegisterModel
{
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string RoleID { get; set; }
public string EntityID { get; set; }
public string MobileNumber { get; set; }
}
Can you please modify your code as below and give it a try:-
var serializedBody = JsonConvert.SerializeObject(r);
var jsonRequestBodyContent = new StringContent(serializedBody, Encoding.UTF8,"application/json");
var client = clientfactory.CreateClient("ServerApi");
var result = await client.PostAsync("/Account/Register",jsonRequestBodyContent);

Call WeatherAPI with HttpClient

I created Web API to receive daily temperature from OpenWeatherAPI.
I put the API call in the MVC project; (plan to create new project later for better microservice architecture.)
Someone had mentioned in the code:
in your HomeController you're attempting to simply just call the action like a method on an instance of WeatherController. You need to use HttpClient there as well. Also, don't new up HttpClient directly. It should be treated as a singleton
How would I conduct this? This is the original code, started programming month ago.
MVC Page:
namespace WeatherPage.Controllers
{
public class HomeController : Controller
{
public WeatherController weathercontroller = new WeatherController();
public IActionResult Index()
{
return View();
}
public Async Task<IActionResult> About()
{
ViewData["Message"] = "Your application description page.";
ViewData["test"] = weathercontroller.City("Seattle");
return View();
}
}
}
API Controller:
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
[HttpGet("[action]/{city}")]
public async Task<IActionResult> City(string city)
{
Rootobject rawWeather = new Rootobject();
using (var client = new HttpClient())
{
try
{
client.BaseAddress = new Uri("http://api.openweathermap.org");
var response = await client.GetAsync($"/data/2.5/weather?q={city}&appid=APIkey&units=metric");
response.EnsureSuccessStatusCode();
var stringResult = await response.Content.ReadAsStringAsync();
rawWeather = JsonConvert.DeserializeObject<Rootobject>(stringResult);
return Ok(rawWeather);
}
catch (HttpRequestException httpRequestException)
{
return BadRequest($"Error getting weather from OpenWeather: {httpRequestException.Message}");
}
}
}
}
public class Rootobject
{
public Coord coord { get; set; }
public Weather[] weather { get; set; }
public string _base { get; set; }
public Main main { get; set; }
public int visibility { get; set; }
public Wind wind { get; set; }
public Clouds clouds { get; set; }
public int dt { get; set; }
public Sys sys { get; set; }
public int id { get; set; }
public string name { get; set; }
public int cod { get; set; }
}
This works in my project:
https://localhost:55555/api/weather/city/washington
Retrieve Data From Third party Openweather Api
Should We Call Web Api from Mvc Application in Same Solution
It roughly means you should use dependency injection .
Don't create an instance of HttpClient every time when you need it , just ask for an instance of HttpClient instead .
Extract your code of getting weather in the weather controller into a service , and ask for the service both in weather controller api and home controller
The WeatherService :
public interface IWeatherService
{
Task<Rootobject> CityAsync(string city);
}
public class WeatherService : IWeatherService{
private HttpClient _httpClient ;
public WeatherService(IHttpClientFactory clientFactory){
this._httpClient = clientFactory.CreateClient();
}
public async Task<Rootobject> CityAsync(string city){
Rootobject rawWeather = new Rootobject();
this._httpClient.BaseAddress = new Uri("http://api.openweathermap.org");
var response = await this._httpClient.GetAsync($"/data/2.5/weather?q={city}&appid=APIkey&units=metric");
response.EnsureSuccessStatusCode();
var stringResult = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Rootobject>(stringResult);
}
}
The new WeatherController :
[Route("api/[controller]")]
public class WeatherController : ControllerBase
{
private IWeatherService _weatherService;
public WeatherController(IWeatherService wetherService ){
this._weatherService= wetherService;
}
[HttpGet("[action]/{city}")]
public async Task<IActionResult> City(string city)
{
try
{
var rawWeather=await this._weatherService.CityAsync(city);
return Ok(rawWeather);
}
catch (HttpRequestException httpRequestException)
{
return BadRequest($"Error getting weather from OpenWeather: {httpRequestException.Message}");
}
}
}
The new HomeController:
public class HomeController : Controller
{
private IWeatherService _weatherService;
public HomeController(IWeatherService wetherService ){
this._weatherService= wetherService;
}
public IActionResult Index()
{
return View();
}
public async Task<IActionResult> About()
{
ViewData["Message"] = "Your application description page.";
ViewData["test"] = await this._weatherService.CityAsync("Seattle");
return View();
}
}
The ConfigureServices:
services.AddHttpClient();
services.AddSingleton<IWeatherService ,WeatherService>();

custom file validation for .net core 2.0

I am trying to make a custom file validation for my project which is based on .net core 2.
I want to validate file size and also file extension in order to prevent users from uploading large files and for example .png files.
I have searched a lot but I could not find anything that works.
Here is my file validation class :
public class FileTypeAttribute : ValidationAttribute, IClientModelValidator
{
private const int MaxSize = 1048576;
private const string _DefaultErrorMessage = "Only the following file types are allowed: {0}";
private IEnumerable<string> _ValidTypes { get; set; }
public string ValidTypes { get; set; }
public string ErrorMessageExtension { get; set; }
public string ErrorMessageSize { get; set; }
public FileTypeAttribute(string errorExtension, string errorSize, string vt)
{
ErrorMessageExtension = errorExtension;
ErrorMessageSize = errorSize;
_ValidTypes = vt.Split(',');
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
IFormFile file = value as IFormFile;
if (file != null)
{
if (!_ValidTypes.Any(e => file.FileName.EndsWith(e)))
{
return new ValidationResult(ErrorMessageExtension);
}
if (file.Length > MaxSize)
{
return new ValidationResult(ErrorMessageSize);
}
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-fileextensions", ErrorMessageExtension);
MergeAttribute(context.Attributes, "data-val-maxfilesize", ErrorMessageSize);
}
private bool MergeAttribute(
IDictionary<string, string> attributes, string key, string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
}
and here is the javascript code in my view:
$.validator.addMethod("FileType",
function (value, element, param) {
for (var i = 0; i < element.files.length; i++) {
var extension = getFileExtension(element.files[0].name);
if ($.inArray(extension, param.validtypes) === -1) {
return false;
}
}
return true;
});
$.validator.unobtrusive.adapters.add('FileType', ['validtypes'], function (options) {
console.log("value:");
options.rules.cannotbered = {};
options.messages["FileType"] = options.message;
});
function getFileExtension(fileName) {
if (/[.]/.exec(fileName)) {
return /[^.]+$/.exec(fileName)[0].toLowerCase();
}
return null;
}
and here is the entity class code in my project:
public class MyEntityClass
{
public int MyEntityClassId { get; set; }
[FileType("invalid format", "invalid size", "jpg,png,gif")]
public IFormFile Photo { get; set; }
}
Can anyone help me to know where the problem is?
Thanks in advance.

MediatR 3.0.1 possible bug? Cannot get IAsyncRequestHandler working

I am getting the following error message when executing IRequest with IAsyncRequestHandler.
System.InvalidOperationException: 'No service for type 'MediatR.IRequestHandler`2[TestProject.Domain.Requests.Users.CreateUserRequest,TestProject.Domain.Requests.Users.CreateUserResponse]' has been registered.'
This is how i register it in the startup class
// Add framework services.
services.AddMvc();
services.AddMediatR(typeof(CreateUserRequest).GetTypeInfo().Assembly);
CreateUserRequest and Response
public class CreateUserRequest : IRequest<CreateUserResponse>
{
public string EmailAddress { get; set; }
public int OrganisationId { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class CreateUserResponse
{
public int UserId { get; set; }
public string EmailAddress { get; set; }
}
Request handler
public class CreateUserRequestHandler : IAsyncRequestHandler<CreateUserRequest, CreateUserResponse>
{
private readonly UserManager<User> _userManager;
public CreateUserRequestHandler()
{
}
public async Task<CreateUserResponse> Handle(CreateUserRequest request)
{
//create the user and assign it to the organisation
var user = new User
{
Email = request.EmailAddress,
OrganisationUsers = new List<OrganisationUser> { new OrganisationUser { OrganisationId = request.OrganisationId } }
};
//create new user with password.
await _userManager.CreateAsync(user, request.Password);
//create response.
var response = new CreateUserResponse{UserId = user.Id, EmailAddress = user.Email};
return response;
}
}
Controller class
public class UserController : Controller
{
private readonly IMediator _mediator;
public UserController(IMediator mediator)
{
_mediator = mediator;
}
[HttpPost]
public async Task<CreateUserResponse> Post(CreateUserRequest request)
{
return await _mediator.Send(request);
}
}
the error occurs inside the controller class it does not hit the async request handler.
Is there anything wrong with the DI registration? I have looked at the examples but could not find anything specific to aspnet core.