ServiceStack - calling 3rd party web service with class - api

I am trying to call a 3rd party web service
Their REST API uses the following URL style.
http://www.VoiceBase.com/services?version=1.0&apikey=your-apikey&password=secret&action=list&status=processing
All of their service calls go to the same /services
How do I create a class so the following would work?
var client = new JsonServiceClient("http://www.voicebase.com");
var response = client.Get<ResponseVoiceBaseListClass>(new VoiceBaseListClass());
Additional classes I have created but I am not quite there yet
public class VoiceBaseBaseClass
{
public string version { get; set; }
public string apikey { get; set; }
public string password { get; set; }
public VoiceBaseBaseClass()
{
this.version = "1.0";
this.apikey = "API";
this.password = "password";
}
}
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
public string action { get; set; }
public string status { get; set; }
public VoiceBaseListClass()
: base()
{
this.action = "list";
this.status = "processing";
}
}
public class ResponseVoiceBaseListClass
{
public string requestStatus { get; set; }
public string statusMessage { get; set; }
public string fileStatus { get; set; }
public List<string> mediaIds { get; set; }
public ResponseVoiceBaseListClass()
{
this.mediaIds = new List<string>();
}
}
Using the above classes the call that goes to the server is
/json/syncreply/VoiceBaseListClass?action=list&status=processing&version=1.0&apikey=API&Password=password
Is there a way I can force the service stack client to go to the
/Services
instead of
/json/syncreply/VoiceBaseListClass

I found a way to do this and it is working great for me.
[RestService("/services", "GET")]
public class VoiceBaseListClass : VoiceBaseBaseClass, IReturn<ResponseVoiceBaseListClass>
{
}
Although this is a deprecated attribute - the new attribute is called Route
https://github.com/ServiceStack/ServiceStack/wiki/Release-Notes
Chris

Related

Getting navigation properties of your entity when you return a CreatedAtRouteResult

Request:
namespace mediere_API.Requests
{
public class LocalitateRequest
{
public string Nume { get; set; }
public int JudetId { get; set; }
}
}
DTO
namespace mediere_API.Dtos
{
public class LocalitateDTO
{
public int Id { get; set; }
public string Nume { get; set; }
public JudetDTO Judet { get; set; }
}
}
Entity
using mediere_API.Dtos;
using System.ComponentModel.DataAnnotations;
namespace mediere_API.DataLayer.Entities
{
public class Localitate : BaseEntity
{
[Required]
public string Nume { get; set; }
[Required]
public int JudetId { get; set; }
public virtual Judet judet { get; set; }
public Localitate() { }
}
}
Processor method
async Task<ActionResult> ILocalitatiProcessor.AddLocalitate(LocalitateRequest localitateRequest)
{
var record = _mapper.Map<Localitate>(localitateRequest);
_unitOfWork.Localitati.Insert(record);
if (await _unitOfWork.SaveChangesAsync() == false)
{
return new BadRequestResult();
}
return new CreatedAtRouteResult("GetByIdLocalitate", new {Id = record.Id}, _mapper.Map<LocalitateDTO>(record));
}
So, I have these pieces of code.
The way I'm using my front-end, I need to have the navigation properties filled in when I get the response on the POST request.
Right now I get:
{
"id": 12777,
"nume": "test",
"judet": null
}
On the get requests it works properly, but with CreatedAtRouteResult it doesn't, and I know why, but I don't know how should I fix it.
Record doesn't have the navigation properties filled in because it is a mapping of localitateRequest (which doesn't have the navigation properties) to Localitate.
So, how should I approach this problem?
Thanks.

How to update an existing entity that has a nested list of entities?

I'm trying to update an entity using entity framework but, everytime I try to do it, it raises an error saying that a nested entity the main class contains cannot be tracked.
These are my classes:
public abstract class BaseEntity
{
public int Id { get; set; }
}
public class Dashboard : BaseEntity
{
public int Order { get; set; }
public string Title { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<Submenu> Submenu { get; set; }
}
public class Submenu : BaseEntity
{
public int Order { get; set; }
public bool Enabled { get; set; }
public string Title { get; set; }
public string Image { get; set; }
public string Descriptions { get; set; }
public virtual ICollection<Action> Actions { get; set; }
public int DashboardId { get; set; }
public virtual Dashboard Dashboard { get; set; }
}
public class Action : BaseEntity
{
public string Type { get; set; }
public string Label { get; set; }
public string Url { get; set; }
public string Extension { get; set; }
public virtual Submenu Submenu { get; set; }
public int SubmenuId { get; set; }
}
The one I am using to update is Dashboard, which contains the rest of the classes.
I'm trying to do it using a generic service layer and a generic repository that are defined this way:
public class GenericService<T> : IGenericService<T> where T : BaseEntity
{
private readonly IBaseRepository<T> baseRepository;
public GenericService(IBaseRepository<T> baseRepository)
{
this.baseRepository = baseRepository;
}
public async Task Update(T entity, T attachedEntity)
{
await baseRepository.Update(entity, attachedEntity);
}
}
public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity
{
private readonly PortalContext dataContext;
private DbSet<T> DbSet { get; set; }
public BaseRepository(PortalContext context)
{
dataContext = context;
DbSet = dataContext.Set<T>();
}
public async Task Update(T entity, T attachedEntity)
{
dataContext.Entry(attachedEntity).State = EntityState.Detached;
DbSet.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
await dataContext.SaveChangesAsync();
}
}
And, at last but no least, this is the way I am configuring everything at Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PortalContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("PortalContext"))
);
services.AddTransient(typeof(IGenericService<>), typeof(GenericService<>));
services.AddTransient(typeof(IBaseRepository<>), typeof(BaseRepository<>));
services.AddTransient<Func<string, ClaimsPrincipal, IRoleCheck>>((serviceProvider) =>
{
return (controllerName, claimsPrincipal) =>
new RoleCheck(serviceProvider.GetRequiredService<IGenericService<Dossier>>(),
serviceProvider.GetRequiredService<IGenericService<DossierTemplate>>(),
serviceProvider.GetRequiredService<IGenericService<Dashboard>>(),
controllerName, claimsPrincipal);
});
}
What the application first does is calling the RoleCheck class to retrieve and filter the required entities and, after that, the user can update them.
When I call the update function at the controller
public async Task<ActionResult<Dashboard>> Put(int id, [FromBody] Dashboard dashboard)
{
var currentDashboard = await service.Get(id);
if (currentDashboard == null)
{
return NotFound();
}
await service.Update(dashboard, currentDashboard);
return Ok();
}
I always receive the next error at the repository:
error
Is there something I am doing wrong? I have been stuck with this for a week now...
Thanks in advance and sorry for the long text, but I wanted it to be clear.
I could finally solve it by adding .AsNoTracking() at the Get() method of my repository:
public async Task<T> Get(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> includes)
{
IQueryable <T> query = DbSet.AsNoTracking();
if (includes != null)
{
query = includes(query);
}
return await query.FirstOrDefaultAsync(m => m.Id == id);
}

Why does my Web Api PUT using Entity Framework 6 keep writing new duplicate records rather than updating them?

I created my database in Entity Framework, and I also created a Web Api that uses Entity Framework. When I perform a GET or a POST (ADD) everything works great, but When I do a PUT (Update) my record is not updated, it is added as if I performed a Post. I think that the following does not recognize that the Entity has been modified:
db.Entry(contact).State = EntityState.Modified;
So, here is my entire Entity Contact.cs created by Entity Framework:
public partial class Contact
{
public int Contact_ID { get; set; }
public int Dataset_ID { get; set; }
public string Booth_UCID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public string Title_Role { get; set; }
public int Contact_Type_ID { get; set; }
public string Email { get; set; }
public string Phone_Number { get; set; }
public string Email_2 { get; set; }
public string Phone_Number_2 { get; set; }
public virtual Contact_Type Contact_Type { get; set; }
public virtual Dataset Dataset { get; set; }
}
Here is the Contact model from my application that is being sent to the Web Api:
public class Contact
{
public int Contact_ID { get; set; }
public int Dataset_ID { get; set; }
public string Booth_UCID { get; set; }
public string First_Name { get; set; }
public string Last_Name { get; set; }
public string Title_Role { get; set; }
public int Contact_Type_ID { get; set; }
public string Email { get; set; }
public string Phone_Number { get; set; }
public string Email_2 { get; set; }
public string Phone_Number_2 { get; set; }
}
And here is my MVC Application to Edit Contact
[HttpPost]
public ActionResult EditContact(Contact contact)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:4251/");
//HTTP POST
// var postTask = client.PostAsJsonAsync<Dataset>("api/datasets/1", dataset);
var postTask = client.PostAsJsonAsync("api/contacts/2", contact);
postTask.Wait();
var result = postTask.Result;
if (result.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
}
ModelState.AddModelError(string.Empty, "Server Error. Please contact administrator.");
return View(contact);
}
and lastly, here is my Web Api with the Entity Framework scafolding: this is straight out of the box, when I created my Web Api
// PUT: api/Contacts/5
[ResponseType(typeof(void))]
public async Task<IHttpActionResult> PutContact(int id, Contact contact)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != contact.Contact_ID)
{
return BadRequest();
}
db.Entry(contact).State = EntityState.Modified;
try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!ContactExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return StatusCode(HttpStatusCode.NoContent);
}
I am at a loss as to what I could possible do. I feel like I should just abandon the Web Api with Entity Framework and just go ahead build an Empty Web Api where I control the update. And if so, how will this be different?
*** Update ***
I fixed this problem and I hope this helps others.
My issue was not within the Web Api or Entity Framework. My issue was in the Request that I was sending to the Web Api.
I wanted to do an Update (PUT), but when I ran this in debug I noticed the PUT method in my Web Api was not being triggered. I put a breakpoint on my POST method and that one was. So, I did a little research and I realized that I need to change the request below:
this line does a POST ADD, which is why I was duplicating my records in the database
var postTask = client.PostAsJsonAsync("api/datasets/2", dataset);
I changed it to the follow to do the Update:
var postTask = client.PutAsJsonAsync<Dataset>("api/datasets/2", dataset);
I thought that the uri I was sending would dictate which method put or post.

Swashbuckle.AspNetCore how to describe error response model?

I have a ASP.NET Core v2.1 with Swashbuckle.AspNetCore package.
I have the following model for error response:
public class ErrorResponse
{
[JsonProperty(PropertyName = "error")]
public Error Error { get; set; }
}
public class Error
{
[JsonProperty(PropertyName = "code")]
public string Code { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "target")]
public string Target { get; set; }
[JsonProperty(PropertyName = "details")]
public List<ErrorDetail> Details { get; set; }
[JsonProperty(PropertyName = "innererror")]
public InnerError InnerError { get; set; }
}
public class ErrorDetail
{
[JsonProperty(PropertyName = "code")]
public string Code { get; set; }
[JsonProperty(PropertyName = "message")]
public string Message { get; set; }
[JsonProperty(PropertyName = "target")]
public string Target { get; set; }
}
public class InnerError
{
[JsonProperty(PropertyName = "code")]
public string Code { get; set; }
[JsonProperty(PropertyName = "innererror")]
public InnerError NestedInnerError { get; set; }
}
so, for example, if something goes wrong, my API endpoint returns object of type ErrorResponse with appropriate StatusCode:
if (String.IsNullOrWhiteSpace(token))
{
ErrorResponse errorResponse = new ErrorResponse() { Error = new Error() };
errorResponse.Error.Code = "InvalidToken";
errorResponse.Error.Target = "token";
errorResponse.Error.Message = "Token is not specified";
return new BadRequestObjectResult(errorResponse);
}
How can i generate appropriate documentations using Swashbuckle.AspNetCore, so, client will know format of response if something goes wrong?
Take a look at the readme:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore#explicit-responses
Explicit Responses
If you need to specify a different status code and/or additional responses, or your actions return IActionResult instead of a response DTO, you can describe explicit responses with the ProducesResponseTypeAttribute that ships with ASP.NET Core. For example ...
[HttpPost("{id}")]
[ProducesResponseType(typeof(Product), 200)]
[ProducesResponseType(typeof(IDictionary<string, string>), 400)]
[ProducesResponseType(500)]
public IActionResult GetById(int id)
So in your case you should add:
[ProducesResponseType(typeof(ErrorResponse), 400)]
To those actions that return the error, here is some good reading:
https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/conventions

Can't correctly add associated objects into Entity Framework Context

I have and entity framework project exposed via a data service:
public class VersionContext : DbContext
{
public DbSet<VersionTreeEntry> VersionTreeEntries { get; set; }
public DbSet<PluginState> PluginStates { get; set; }
public static void SetForUpdates()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<VersionContext, Configuration>());
}
}
public class VersionTreeEntry
{
public VersionTreeEntry()
{
Children = new List<VersionTreeEntry>();
PluginStates = new List<PluginState>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public virtual ICollection<VersionTreeEntry> Children { get; set; }
public virtual ICollection<PluginState> PluginStates { get; set; }
public virtual VersionTreeEntry Ancestor { get; set; }
/// <summary>
/// Links to the ProtoBufDataItem Id for the session state.
/// </summary>
public int DataId { get; set; }
public string Notes { get; set; }
[Required]
public DateTime TimeStamp { get; set; }
[MinLength(1, ErrorMessage = "Tag cannot have a zero length")]
[MaxLength(20, ErrorMessage = "A tag name cannot contain over 20 characters")]
public string Tag { get; set; }
public bool IsUiNodeExpanded { get; set; }
[Required]
public string Version { get; set; }
[Required]
public string SessionName { get; set; }
}
public class PluginState
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string PluginName { get; set; }
[Required]
public byte[] Data { get; set; }
}
As far as I can see, the data classes are defined correctly. I try to create some new objects and add them into the context, with their relations intact:
var session = new Session();
session.SessionName = "My new session";
VersionTreeEntry versionTreeEntry = new VersionTreeEntry();
versionTreeEntry.SessionName = session.SessionName;
versionTreeEntry.Version = Assembly.GetExecutingAssembly().GetName().Version.ToString();
versionTreeEntry.TimeStamp = DateTime.Now;
_versionContext.AddToVersionTreeEntries(versionTreeEntry);
foreach (var plugin in session.Plugins)
{
using (var ms = new MemoryStream())
{
plugin.SaveState(ms);
PluginState state = new PluginState();
state.PluginName = plugin.PluginName;
state.Data = ms.ToArray();
versionTreeEntry.PluginStates.Add(state);
}
}
_versionContext.SaveChanges();
The problem is that the PluginState instances never actually get added to the database. If I add code to add them manually to the context, they do get added, but the foreign key pointing back to the VersionTreeEntry is null.
Again, this is a WCF DataService rather than vanilla EF, any idea what might be wrong?
Cheers
Posting the answer here from the comment section.
Agreed. The best way to do this is to call the following API:
_versionContext.AddRelatedObject(versionTreeEntry, "PluginStates", state);
Thanks
Pratik